mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-09-27 14:30:27 +00:00
Compare commits
499 commits
Author | SHA1 | Date | |
---|---|---|---|
|
ceb88d960f | ||
|
5f055160ba | ||
|
32aaf19fa3 | ||
|
e006c20556 | ||
|
0d160d8cf6 | ||
|
87c6719e1d | ||
|
f532d523b2 | ||
|
b5586095bc | ||
|
5c66d8c107 | ||
|
f70482d9bc | ||
|
bd7b94e305 | ||
|
8313260e09 | ||
|
c0f3eff18e | ||
|
1be92c4b86 | ||
|
0c8b84d8a8 | ||
|
4d71525eee | ||
|
431adaa2f9 | ||
|
b5e0e0713c | ||
|
1ff761e410 | ||
|
d5a9c7a940 | ||
|
a85b0cb72e | ||
|
c9b370a6e4 | ||
|
d69d0f8738 | ||
|
29b54ed2fc | ||
|
7905626a5f | ||
|
6a23ae168f | ||
|
db026ad535 | ||
|
aae9d5c0e9 | ||
|
1675e517b3 | ||
|
1d20028b00 | ||
|
975e4e157b | ||
|
6a7118e535 | ||
|
7843209692 | ||
|
c505d9a418 | ||
|
0c9fb369d3 | ||
|
fef6601094 | ||
|
24003a79f6 | ||
|
c32cb20906 | ||
|
c5b1ebc7d8 | ||
|
44f64fb3f6 | ||
|
453b3014e6 | ||
|
8fd2b7daa4 | ||
|
2e0f52de2c | ||
|
7a959b9e9d | ||
|
a709eb96d9 | ||
|
295b9f01c2 | ||
|
bfc32cc692 | ||
|
65508cfe75 | ||
|
e72db57179 | ||
|
871756bb70 | ||
|
ee4416ee5f | ||
|
ab3db748be | ||
|
0c4ec370cf | ||
|
57821cade4 | ||
|
04da3b2047 | ||
|
84fef267b5 | ||
|
f2658eb773 | ||
|
0135c4251c | ||
|
c57b6c9c0a | ||
|
3c38ed7ff0 | ||
|
fc29ff7d8b | ||
|
52895223a8 | ||
|
7e9a6d6893 | ||
|
2f9bb62b6b | ||
|
f0d42b88cb | ||
|
f3206c2e1a | ||
|
d274caeb35 | ||
|
66727188cf | ||
|
7e912b0dde | ||
|
3fd3a32b6f | ||
|
4f01421cc4 | ||
|
ff5032934d | ||
|
602760d0d8 | ||
|
320f36a462 | ||
|
4cf93ccbdb | ||
|
87b72f768b | ||
|
dee0e32dde | ||
|
8ad882bed5 | ||
|
c4bcdea830 | ||
|
798936afc9 | ||
|
ad0a23fee7 | ||
|
c43fc2d576 | ||
|
b9fcb99cd4 | ||
|
b4f22a52ff | ||
|
5dc2d56c0e | ||
|
16ee51621e | ||
|
b6406013c5 | ||
|
170e769812 | ||
|
4f69dcd210 | ||
|
982a9a9aea | ||
|
5172e8e520 | ||
|
eb0a44fe67 | ||
|
1c48d7065d | ||
|
01e28ddfe2 | ||
|
0a4dc29efe | ||
|
086281b03d | ||
|
0a6963f7ce | ||
|
102185d09d | ||
|
ede82ca5b4 | ||
|
e21f341a03 | ||
|
72e53b9f16 | ||
|
ea29052c39 | ||
|
3e97fef6ce | ||
|
bc930122ba | ||
|
0da1c8e9c9 | ||
|
30a5987c9e | ||
|
de42ae432c | ||
|
c5163a73ee | ||
|
31e836f4d6 | ||
|
914ffc8be9 | ||
|
c554a5dc76 | ||
|
035a199109 | ||
|
9080c90120 | ||
|
a9ff9615ff | ||
|
64f0b76f71 | ||
|
9455e09d9f | ||
|
b709c56478 | ||
|
6c04b59454 | ||
|
ec38d416aa | ||
|
9006a47e9b | ||
|
cfe9968a77 | ||
|
17910dd532 | ||
|
ba0265970e | ||
|
df330093d5 | ||
|
b83b6031e5 | ||
|
184778d087 | ||
|
3a949db720 | ||
|
2333b241f0 | ||
|
fa060b9fa0 | ||
|
1316b821c4 | ||
|
a05ab37b49 | ||
|
86039dd5c1 | ||
|
79657e5671 | ||
|
380448587b | ||
|
5a1d12419f | ||
|
34b791ff5e | ||
|
9c84748fc3 | ||
|
d4d02d70a8 | ||
|
d20ffd5d39 | ||
|
fdfa3a33d9 | ||
|
3a8462367e | ||
|
276ec91cb2 | ||
|
6e9855c36b | ||
|
000c486568 | ||
|
12be9a24a6 | ||
|
797dd3f3ca | ||
|
a8ccfe49d9 | ||
|
73fa904a7b | ||
|
c7ef8e8185 | ||
|
2238db2005 | ||
|
3609411801 | ||
|
98b28d69ce | ||
|
f88f5b03c4 | ||
|
4123b5d1a1 | ||
|
8522c8a445 | ||
|
2fe852166e | ||
|
6e974cf4b9 | ||
|
195c089f18 | ||
|
ac0e24b2bd | ||
|
4ab8d92f28 | ||
|
c701aa6f84 | ||
|
bd2a039c8d | ||
|
eee93aea52 | ||
|
95ae67752f | ||
|
0ef886ea16 | ||
|
f303992e0c | ||
|
718e757669 | ||
|
f0df6874d8 | ||
|
960529d90d | ||
|
bbf131086a | ||
|
7caf6b2073 | ||
|
505fab2e1c | ||
|
a10577b42c | ||
|
0ecbd3f953 | ||
|
3b7b2cd37b | ||
|
0bd98e2c34 | ||
|
e00ebca63f | ||
|
2b35f009fb | ||
|
cf7172248c | ||
|
bc5ed023e4 | ||
|
dbad98132f | ||
|
39b61195ad | ||
|
10a31a397e | ||
|
d036abb7d2 | ||
|
df4a4fb2ef | ||
|
2d1f556794 | ||
|
84a9f9c61f | ||
|
47d62b6d78 | ||
|
90e926def4 | ||
|
345edeb947 | ||
|
a7764ff033 | ||
|
9b323a6519 | ||
|
23d998a1db | ||
|
8f96509f03 | ||
|
59bada0a9f | ||
|
743ab29ba8 | ||
|
970d1c9afd | ||
|
5aedcab32f | ||
|
4677948a82 | ||
|
d357a63bf9 | ||
|
5cd9e34265 | ||
|
bbe38b9599 | ||
|
b4b56eb282 | ||
|
343680ffea | ||
|
477855789d | ||
|
93c9821cba | ||
|
0ca4a3778a | ||
|
69c3c2ae46 | ||
|
cd47bf2f04 | ||
|
6538803cf6 | ||
|
4c9ed330c8 | ||
|
7f16fd7736 | ||
|
3e4330686f | ||
|
3b6832724f | ||
|
968e0fddb9 | ||
|
39f466f2c6 | ||
|
4eed615871 | ||
|
3d4d785a2a | ||
|
51f6d3986f | ||
|
00aaecad07 | ||
|
c42040fbb8 | ||
|
9945b702b8 | ||
|
f68655b5e2 | ||
|
aaccc6e7f1 | ||
|
f30cb2b56c | ||
|
7cec628c43 | ||
|
0e85973e94 | ||
|
30252a1b2e | ||
|
1e964233c6 | ||
|
c9ac553cfe | ||
|
260b04a1cf | ||
|
ba70bb1154 | ||
|
85c38107cf | ||
|
8171a00943 | ||
|
ab2f5e3d8d | ||
|
2b68920f82 | ||
|
6597ec84eb | ||
|
6b628485c5 | ||
|
802ff6a67c | ||
|
8fc652f208 | ||
|
568e8533fa | ||
|
91bc39367b | ||
|
04e9e5284c | ||
|
1c54c77840 | ||
|
83f76280f5 | ||
|
712d4757c3 | ||
|
de726ca8d2 | ||
|
ebdcc403cf | ||
|
45800d7636 | ||
|
a7418fb483 | ||
|
df32e1ebfa | ||
|
525179f666 | ||
|
9485265769 | ||
|
1600d3b055 | ||
|
0121d78482 | ||
|
d480c6c2d3 | ||
|
7d5789032a | ||
|
06f40e72cb | ||
|
48e7a2ed06 | ||
|
66306e32f2 | ||
|
327f563e80 | ||
|
74ec83a0ff | ||
|
1865899621 | ||
|
2b4ec75bc5 | ||
|
e09ad990fa | ||
|
1e4a966c92 | ||
|
66c9840ad8 | ||
|
2c86f18a99 | ||
|
27ad26c258 | ||
|
984a9fe5ff | ||
|
b4fd6cf362 | ||
|
4f74cb7958 | ||
|
b6e24668a7 | ||
|
92a1e222f4 | ||
|
de71e9dadd | ||
|
be7da027f8 | ||
|
1e33926dc5 | ||
|
c2f67bd3c9 | ||
|
e7d0e0702a | ||
|
566e6443f4 | ||
|
4259d284bd | ||
|
58e91c154c | ||
|
28bd6f07a2 | ||
|
b1ad123595 | ||
|
c99cabfbc5 | ||
|
f5a7de9dc3 | ||
|
b12da2c543 | ||
|
02cd2c42fd | ||
|
dcc0b47349 | ||
|
0d33077df6 | ||
|
16608d2541 | ||
|
bab3498c6a | ||
|
72006215cb | ||
|
10e0294d5a | ||
|
61523baa7b | ||
|
6f871e6ce2 | ||
|
92c0cf1285 | ||
|
2585639054 | ||
|
0215339c5a | ||
|
539000574b | ||
|
bac5845be1 | ||
|
c282bc1bca | ||
|
9a7f37e2b7 | ||
|
71e9c2bb04 | ||
|
d9aa0731f4 | ||
|
49d3dd17a2 | ||
|
71cd80f204 | ||
|
5549dc7a15 | ||
|
613ed56675 | ||
|
a719cbfcc6 | ||
7d75e263f8 | |||
|
aabb011f5a | ||
|
e845e3575c | ||
|
f265c3197b | ||
|
e8e173d0d0 | ||
|
f842aff6df | ||
|
7e09481adc | ||
|
1b48fb7ae7 | ||
|
fe55acb4c9 | ||
|
fe3607bd14 | ||
|
5884c00bd0 | ||
|
13c3db7857 | ||
|
2b7488a4c8 | ||
|
b4576a0074 | ||
|
8861fc493b | ||
|
2bfb6ee016 | ||
|
edd7c258c8 | ||
|
3a3cec96ff | ||
|
80f8664564 | ||
|
be3ae583bc | ||
|
58106a42a9 | ||
|
096538989b | ||
|
150ad7a545 | ||
|
0d2f054c15 | ||
|
18cf5292b7 | ||
|
97d8a79d36 | ||
|
a306b1ce94 | ||
|
22c6a98914 | ||
|
8b64c734e7 | ||
|
befd8d4bd2 | ||
|
ce930eab5f | ||
|
75b25d011f | ||
|
953f6a3fd7 | ||
|
8e675de690 | ||
|
4326c3bfce | ||
|
47b788d44b | ||
|
16b0a4d762 | ||
|
b588ee59bc | ||
|
5466cafc24 | ||
|
812fe0a9bd | ||
|
c55c4ca42a | ||
|
83bd7be92a | ||
|
70397a9f05 | ||
|
a87eaa4b79 | ||
|
88cbc93338 | ||
|
927c3fcdb6 | ||
|
5803904deb | ||
|
c95e07a897 | ||
|
b42bd3d026 | ||
|
3dd800ac77 | ||
|
c92462b240 | ||
|
7573caa8e9 | ||
|
c12585377c | ||
|
17d7997137 | ||
|
66030f36ad | ||
|
b3d3895ae7 | ||
|
d6a855ff1b | ||
|
542030fd82 | ||
|
3fc38be5c4 | ||
|
168af88eda | ||
|
83d70d3471 | ||
|
596a9177ce | ||
|
2341ee6935 | ||
|
61c9cbdc8f | ||
|
00b56ca845 | ||
|
42158cbcb0 | ||
|
4dcc44687a | ||
|
fbce73f6fc | ||
|
f0c38621c1 | ||
|
a3e30b499f | ||
|
2ad452ee89 | ||
|
5d939498f1 | ||
|
f4b086738b | ||
|
6b30266145 | ||
|
c8180e714e | ||
|
0b356ee203 | ||
|
c2ebb3083a | ||
|
921938fd20 | ||
|
fab246f82e | ||
|
7757e06e36 | ||
|
70adedb142 | ||
|
7f6929b98d | ||
|
2575013faa | ||
|
d8fe1c64f1 | ||
|
fea85ff9c8 | ||
|
cc43935036 | ||
|
b5cbc47cf7 | ||
|
35b84d219f | ||
|
0aabbb3186 | ||
|
4dd6b102c4 | ||
|
0dd03da91f | ||
|
e1cd52178e | ||
|
55b4de779c | ||
|
9db4290d2d | ||
|
df30d2fbd3 | ||
|
b0cf7e5c81 | ||
|
756abbf807 | ||
|
5d7e068a8b | ||
|
a870d60621 | ||
|
9f27bde36a | ||
|
e868f81189 | ||
|
bac2e02160 | ||
|
ae7c68dbf8 | ||
|
0b11209674 | ||
|
f97150aa58 | ||
|
7e1ab086de | ||
|
be12c0a5f7 | ||
|
317f46ad97 | ||
|
c5e7e76e4d | ||
|
6556d31ab8 | ||
|
5476e3d759 | ||
|
cc7b7d508d | ||
|
2b9272c7eb | ||
|
cca3ebf520 | ||
|
428f670753 | ||
|
fadb7d0a26 | ||
|
2a88e29454 | ||
|
bfff0f7d87 | ||
|
96337d5234 | ||
|
eb49459937 | ||
|
ce3960f37f | ||
|
9f07ec35e6 | ||
|
f4366f8b2e | ||
|
523a46b4f5 | ||
|
6f8fc5f178 | ||
|
8f997ea4e3 | ||
|
992f8d9a5d | ||
|
9c6a39d692 | ||
|
b29a739fb2 | ||
|
1dea8f60a8 | ||
|
2629719b4e | ||
|
9e6e8c618e | ||
|
995f64513d | ||
|
5b01e43a12 | ||
|
03abb5c681 | ||
|
7a46377627 | ||
|
15e7a63e7b | ||
|
612f863ee9 | ||
|
237f22d131 | ||
|
2839e0078b | ||
|
0414f468c6 | ||
|
8b0731b5a2 | ||
|
7d0397e1ad | ||
|
f6476f1e8f | ||
|
cfebc32b82 | ||
|
721b7e9c8c | ||
|
1e88971ec8 | ||
|
8a6bcb712f | ||
|
002dc36ab9 | ||
|
9c590f4223 | ||
|
f0b408d823 | ||
|
17b2640237 | ||
|
fa006b9fc9 | ||
|
96037fbcc5 | ||
|
730b3459f1 | ||
|
60bb72ddc3 | ||
|
eabf31e6d0 | ||
|
550018c917 | ||
|
0829898d73 | ||
|
ec17c58dee | ||
|
563eff1193 | ||
|
594400a7f5 | ||
|
47ba068966 | ||
|
5df7c01cb5 | ||
|
f7ffa13543 | ||
|
23955d2dbb | ||
|
340d65d7a4 | ||
|
b9195ed309 | ||
|
fc1c017fc6 | ||
|
f563f8334b | ||
|
e09f9e9540 | ||
|
da21dc853d | ||
|
2228f882d8 | ||
|
8f3a6171ac | ||
|
2572afbf15 | ||
|
0faac3b875 | ||
|
cb0cc764ba | ||
|
45f55423fb | ||
|
8ef12a72e8 | ||
|
05884de50c | ||
|
67b7cf9764 | ||
|
9827106961 | ||
|
b2d5ee48cd | ||
|
7274c725a6 | ||
|
66ad059a47 | ||
|
c0d111e2c1 | ||
|
9116853e6d | ||
|
21aa61b69c | ||
|
119d905805 |
513 changed files with 84349 additions and 19006 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,3 @@
|
|||
Cargo.lock
|
||||
target
|
||||
*~
|
||||
*.bk
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.templates_sha: &templates_sha fddab8aa63e89a8e65214f59860d9c0f030360c9
|
||||
.templates_sha: &templates_sha 6a40df92957c8ce9ee741aaccc5daaaf70545b1e
|
||||
|
||||
include:
|
||||
- project: 'freedesktop/ci-templates'
|
||||
|
@ -20,9 +20,11 @@ variables:
|
|||
# to ensure that we are testing against the same thing as GStreamer itself.
|
||||
# The tag name is included above from the main repo.
|
||||
GSTREAMER_DOC_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer/amd64/fedora:$FEDORA_TAG-main"
|
||||
# Use the gstreamer image to trigger the cerbero job, same as the monorepo
|
||||
CERBERO_TRIGGER_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer/amd64/fedora:$FEDORA_TAG-main"
|
||||
WINDOWS_BASE: "registry.freedesktop.org/gstreamer/gstreamer-rs/windows"
|
||||
WINDOWS_RUST_MINIMUM_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_MSRV"
|
||||
WINDOWS_RUST_STABLE_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
||||
WINDOWS_RUST_MINIMUM_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_MSRV"
|
||||
WINDOWS_RUST_STABLE_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_STABLE"
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
|
@ -36,6 +38,14 @@ workflow:
|
|||
|
||||
default:
|
||||
interruptible: true
|
||||
# Auto-retry jobs in case of infra failures
|
||||
retry:
|
||||
max: 1
|
||||
when:
|
||||
- 'runner_system_failure'
|
||||
- 'stuck_or_timeout_failure'
|
||||
- 'scheduler_failure'
|
||||
- 'api_failure'
|
||||
|
||||
stages:
|
||||
- "trigger"
|
||||
|
@ -50,6 +60,7 @@ trigger:
|
|||
stage: 'trigger'
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
tags: [ 'placeholder-job' ]
|
||||
script:
|
||||
- echo "Trigger job done, now running the pipeline."
|
||||
rules:
|
||||
|
@ -72,7 +83,7 @@ trigger:
|
|||
- rm -rf target
|
||||
before_script:
|
||||
- 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.toml
|
||||
|
||||
.debian:12-stable:
|
||||
extends: .debian:12
|
||||
|
@ -94,13 +105,14 @@ trigger:
|
|||
RUST_BACKTRACE: 'full'
|
||||
script:
|
||||
- rustc --version
|
||||
- CARGO_FLAGS="-j${FDO_CI_CONCURRENT:-$(nproc)} --locked --color=always --all --all-targets"
|
||||
|
||||
- cargo build --locked --color=always --workspace --all-targets
|
||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets
|
||||
- cargo build --locked --color=always --workspace --all-targets --all-features --exclude gst-plugin-gtk4
|
||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --all-features --exclude gst-plugin-gtk4
|
||||
- cargo build --locked --color=always --workspace --all-targets --no-default-features
|
||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --no-default-features
|
||||
- cargo build $CARGO_FLAGS
|
||||
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test $CARGO_FLAGS
|
||||
- cargo build $CARGO_FLAGS --all-features --exclude gst-plugin-gtk4
|
||||
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test $CARGO_FLAGS --all-features --exclude gst-plugin-gtk4
|
||||
- cargo build $CARGO_FLAGS --no-default-features
|
||||
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test $CARGO_FLAGS --no-default-features
|
||||
|
||||
test msrv:
|
||||
extends:
|
||||
|
@ -261,6 +273,7 @@ documentation:
|
|||
- 'docker'
|
||||
- 'windows'
|
||||
- '2022'
|
||||
- "gstreamer-windows"
|
||||
script:
|
||||
# Set the code page to UTF-8
|
||||
- chcp 65001
|
||||
|
@ -287,6 +300,7 @@ test windows stable:
|
|||
rustfmt:
|
||||
extends: '.debian:12-stable'
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
needs: []
|
||||
script:
|
||||
- cargo fmt --version
|
||||
|
@ -295,6 +309,7 @@ rustfmt:
|
|||
typos:
|
||||
extends: '.debian:12-stable'
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
needs: []
|
||||
script:
|
||||
- typos
|
||||
|
@ -302,6 +317,7 @@ typos:
|
|||
gstwebrtc-api lint:
|
||||
image: node:lts
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
needs: []
|
||||
script:
|
||||
- cd net/webrtc/gstwebrtc-api
|
||||
|
@ -311,10 +327,12 @@ gstwebrtc-api lint:
|
|||
check commits:
|
||||
extends: '.debian:12-stable'
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
needs: []
|
||||
script:
|
||||
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
||||
- ci/check-for-symlinks.sh
|
||||
- ci/check-meson-version.sh
|
||||
|
||||
clippy:
|
||||
extends: '.debian:12-stable'
|
||||
|
@ -326,9 +344,10 @@ clippy:
|
|||
# csound-sys only looks at /usr/lib and /usr/local top levels
|
||||
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
||||
script:
|
||||
- cargo clippy --locked --color=always --all --all-targets -- -D warnings -A unknown-lints
|
||||
- cargo clippy --locked --color=always --all --all-features --all-targets --exclude gst-plugin-gtk4 -- -D warnings -A unknown-lints
|
||||
- cargo clippy --locked --color=always --all --all-targets --no-default-features -- -D warnings -A unknown-lints
|
||||
- CARGO_FLAGS="-j${FDO_CI_CONCURRENT:-$(nproc)} --locked --color=always --all --all-targets"
|
||||
- cargo clippy $CARGO_FLAGS -- -D warnings -A unknown-lints
|
||||
- cargo clippy $CARGO_FLAGS --all-features --exclude gst-plugin-gtk4 -- -D warnings -A unknown-lints
|
||||
- cargo clippy $CARGO_FLAGS --no-default-features -- -D warnings -A unknown-lints
|
||||
|
||||
deny:
|
||||
extends: .debian:12-stable
|
||||
|
@ -353,7 +372,9 @@ outdated:
|
|||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
script:
|
||||
- cargo update --color=always
|
||||
- cargo outdated --color=always --root-deps-only --exit-code 1 -v
|
||||
# Ignore bitstream-io until we can update MSRV to 1.80
|
||||
# Ignore test-with until we can update MSRV to 1.77
|
||||
- cargo outdated --color=always --root-deps-only --exit-code 1 -v -i bitstream-io -i test-with
|
||||
|
||||
coverage:
|
||||
allow_failure: true
|
||||
|
@ -369,18 +390,53 @@ coverage:
|
|||
# csound-sys only looks at /usr/lib and /usr/local top levels
|
||||
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
||||
script:
|
||||
- cargo test --locked --color=always --all --all-features --exclude gst-plugin-gtk4
|
||||
- CARGO_FLAGS="-j${FDO_CI_CONCURRENT:-$(nproc)} --locked --color=always --all"
|
||||
|
||||
- cargo test $CARGO_FLAGS --all-features --exclude gst-plugin-gtk4
|
||||
# generate html report
|
||||
- grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "*target*" --ignore "*/build.rs" -o ./coverage/
|
||||
# generate cobertura report for gitlab integration
|
||||
- grcov . --binary-path ./target/debug/ -s . -t cobertura --branch --ignore-not-existing --ignore "*target*" --ignore "*/build.rs" -o coverage.xml
|
||||
- mkdir -p coverage
|
||||
- grcov . --binary-path ./target/debug/ -s . -t html,cobertura --branch --ignore-not-existing --ignore "*target*" --ignore "*/build.rs" -o ./coverage/
|
||||
# output coverage summary for gitlab parsing.
|
||||
# TODO: use grcov once https://github.com/mozilla/grcov/issues/556 is fixed
|
||||
- grep "%" coverage/index.html | head -1 || true
|
||||
- grep "%" coverage/html/index.html | head -1 || true
|
||||
artifacts:
|
||||
paths:
|
||||
- 'coverage'
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
path: "coverage/cobertura.xml"
|
||||
|
||||
cerbero trigger:
|
||||
image: $CERBERO_TRIGGER_IMAGE
|
||||
needs: [ "trigger" ]
|
||||
timeout: '4h'
|
||||
tags:
|
||||
- placeholder-job
|
||||
variables:
|
||||
# We will build this cerbero branch in the cerbero trigger CI
|
||||
CERBERO_UPSTREAM_BRANCH: 'main'
|
||||
script:
|
||||
- ci/cerbero/trigger_cerbero_pipeline.py
|
||||
rules:
|
||||
# Never run post merge
|
||||
- if: '$CI_PROJECT_NAMESPACE == "gstreamer"'
|
||||
when: never
|
||||
# Don't run if the only changes are files that cargo-c does not read
|
||||
- if:
|
||||
changes:
|
||||
- "CHANGELOG.md"
|
||||
- "README.md"
|
||||
- "deny.toml"
|
||||
- "rustfmt.toml"
|
||||
- "typos.toml"
|
||||
- "*.py"
|
||||
- "*.sh"
|
||||
- "Makefile"
|
||||
- "meson.build"
|
||||
- "meson_options.txt"
|
||||
- "**/meson.build"
|
||||
- "ci/*.sh"
|
||||
- "ci/*.py"
|
||||
when: never
|
||||
- when: always
|
||||
|
|
204
CHANGELOG.md
204
CHANGELOG.md
|
@ -5,6 +5,198 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
|
||||
specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-version-field).
|
||||
|
||||
## [0.13.1] - 2024-08-27
|
||||
### Fixed
|
||||
- transcriberbin: Fix gst-inspect with missing elements.
|
||||
- gtk4paintablesink: Move dmabuf cfg to the correct bracket level.
|
||||
- webrtcsrc: Don't hold the state lock while removing sessions.
|
||||
- rtpbasepay: Various fixes to payloader base class.
|
||||
- webrtcsink: Fix various assertions when finalizing.
|
||||
- webrtcsrc: Make sure to always call end_session() without state lock.
|
||||
- mpegtslivesrc: Handle PCR discontinuities as errors.
|
||||
- ndisrc: Calculate timestamps for metadata buffers too.
|
||||
- Various new clippy warnings.
|
||||
- webrtcsink: Fix segment format mismatch when using a remote offer.
|
||||
- awstranscriber: Fix sanity check in transcribe loop.
|
||||
- whepsrc: Fix incorrect default caps.
|
||||
|
||||
### Changed
|
||||
- gtk4paintablesink: Enable `gtk::GraphicsOffload::black-background` when
|
||||
building with GTK 4.16 or newer.
|
||||
- gstwebrtc-api: Always include index file in dist for convenience.
|
||||
- rtpbasepay: Negotiate SSRC/PT with downstream via caps for backwards
|
||||
compatibility.
|
||||
- hlssink3: Use more accurate fragment duration from splitmuxsink if
|
||||
available.
|
||||
|
||||
### Added
|
||||
- gtk4paintablesink: Add `window-width` and `window-height` properties.
|
||||
- gtk4paintablesink: Add custom widget for automatically updating window size.
|
||||
- fmp4mux / mp4mux: Add image orientation tag support.
|
||||
- webrtcsink: Add nvv4l2av1enc support.
|
||||
- cmafmux: Add Opus support.
|
||||
|
||||
## [0.13.0] - 2024-07-16
|
||||
|
||||
### Added
|
||||
- rtp: New RTP payloader and depayloader base classes, in addition to new
|
||||
payloader and depayloaders for: PCMA, PCMU, AC-3, AV1 (ported to the new
|
||||
base classes), MPEG-TS, VP8, VP9, MP4A, MP4G, JPEG, Opus, KLV.
|
||||
- originalbuffer: New pair of elements that allows to save a buffer, perform
|
||||
transformations on it and then restore the original buffer but keeping any
|
||||
new analytics and other metadata on it.
|
||||
- gopbuffer: New element for buffering an entire group-of-pictures.
|
||||
- tttocea708: New element for converting timed text to CEA-708 closed captions.
|
||||
- cea708mux: New element for muxing multiple CEA-708 services together.
|
||||
- transcriberbin: Add support for generating CEA-708 closed captions and
|
||||
CEA-608-in-708.
|
||||
- cea708overlay: New overlay element for CEA-708 and CEA-608 closed captions.
|
||||
- dav1ddec: Signal colorimetry in the caps.
|
||||
- webrtc: Add support for RFC7273 clock signalling and synchronization to
|
||||
webrtcsrc and webrtcsink.
|
||||
- tracers: Add a new pad push durations tracer.
|
||||
- transcriberbin: Add support for a secondary audio stream.
|
||||
- quinn: New plugin with a QUIC source and sink element.
|
||||
- rtpgccbwe: New mode based on linear regression instead of a kalman filter.
|
||||
- rtp: New rtpsend and rtprecv elements that provide a new implementation of
|
||||
the rtpbin element with a separate send and receive side.
|
||||
- rtpsrc2: Add support for new rtpsend / rtprecv elements instead of rtpbin.
|
||||
- webrtcsrc: Add multi-producer support.
|
||||
- livesync: Add sync property for enabling/disabling syncing of the output
|
||||
buffers to the clock.
|
||||
- mpegtslivesrc: New element for receiving an MPEG-TS stream, e.g. over SRT or
|
||||
UDP, and exposing the remote PCR clock as a local GStreamer clock.
|
||||
- gtk4paintablesink: Add support for rotations / flipping.
|
||||
- gtk4paintablesink: Add support for RGBx formats in non-GL mode.
|
||||
|
||||
### Fixed
|
||||
- livesync: Queue up to latency buffers instead of requiring a queue of the
|
||||
same size in front of livesync.
|
||||
- livesync: Synchronize the first buffer to the clock too.
|
||||
- livesync: Use correct duration for deciding whether a filler has to be
|
||||
inserted or not.
|
||||
- audioloudnorm: Fix possible off-by-one in the limiter when handling the very
|
||||
last buffer.
|
||||
- webrtcsink: Fix property types for rav1enc.
|
||||
|
||||
### Changed
|
||||
- sccparse, mccparse: Port from nom to winnow.
|
||||
- uriplaylistbin: Rely on uridecodebin3 gapless logic instead of
|
||||
re-implementing it.
|
||||
- webrtc: Refactor of JavaScript API.
|
||||
- janusvrwebrtcsink: New use-string-ids property to distinguish between
|
||||
integer and string room IDs, instead of always using strings and guessing
|
||||
what the server expects.
|
||||
- janusvrwebrtcsink: Handle more events and expose some via signals.
|
||||
- dav1ddec: Require dav1d 1.3.0.
|
||||
- closedcaption: Drop libcaption C code and switch to a pure Rust
|
||||
implementation.
|
||||
|
||||
## [0.12.7] - 2024-06-19
|
||||
### Fixed
|
||||
- aws, spotifyaudiosrc, reqwesthttpsrc, webrtchttp: Fix race condition when unlocking
|
||||
- rtp: Allow any payload type for the AV1 RTP payloader/depayloader
|
||||
- rtp: Various fixes to the AV1 RTP payloader/depayloader to work correctly
|
||||
with Chrome and Pion
|
||||
- meson: Various fixes to the meson-based build system around cargo
|
||||
- webrtcsink: Use correct property names for configuring `av1enc`
|
||||
- webrtcsink: Avoid lock poisoning when setting encoder properties
|
||||
|
||||
### Added
|
||||
- ndi: Support for NDI SDK v6
|
||||
- webrtcsink: Support for AV1 via `nvav1enc`, `av1enc` or `rav1enc`
|
||||
|
||||
### Changed
|
||||
- Update to async-tungstenite 0.26
|
||||
|
||||
## [0.12.6] - 2024-05-23
|
||||
### Fixed
|
||||
- Various Rust 1.78 clippy warnings.
|
||||
- gtk4paintablesink: Fix plugin description.
|
||||
|
||||
### Added
|
||||
- fmp4mux / mp4mux: Add support for adding AV1 header OBUs into the MP4
|
||||
headers.
|
||||
- fmp4mux / mp4mux: Take track language from the tags if provided.
|
||||
- gtk4paintablesink: Add GST_GTK4_WINDOW_FULLSCREEN environment variable to
|
||||
create a fullscreen window for debugging purposes.
|
||||
- gtk4paintablesink: Also create a window automatically when called from
|
||||
gst-play-1.0.
|
||||
- webrtc: Add support for insecure TLS connections.
|
||||
- webrtcsink: Add VP9 parser after the encoder.
|
||||
|
||||
### Changed
|
||||
- webrtcsink: Improve error when no discovery pipeline runs.
|
||||
- rtpgccbwe: Improve debug output in various places.
|
||||
|
||||
## [0.12.5] - 2024-04-29
|
||||
### Fixed
|
||||
- hrtfrender: Use a bitmask instead of an int in the caps for the channel-mask.
|
||||
- rtpgccbwe: Don't log an error when pushing a buffer list fails while stopping.
|
||||
- webrtcsink: Don't panic in bitrate handling with unsupported encoders.
|
||||
- webrtcsink: Don't panic if unsupported input caps are used.
|
||||
- webrtcsrc: Allow a `None` producer-id in `request-encoded-filter` signal.
|
||||
|
||||
### Added
|
||||
- aws: New property to support path-style addressing.
|
||||
- fmp4mux / mp4mux: Support FLAC instead (f)MP4.
|
||||
- gtk4: Support directly importing dmabufs with GTK 4.14.
|
||||
- gtk4: Add force-aspect-ratio property similar to other video sinks.
|
||||
|
||||
## [0.12.4] - 2024-04-08
|
||||
### Fixed
|
||||
- aws: Use fixed behaviour version to ensure that updates to the AWS SDK don't
|
||||
change any defaults configurations in unexpected ways.
|
||||
- onvifmetadataparse: Fix possible deadlock on shutdown.
|
||||
- webrtcsink: Set `perfect-timestamp=true` on audio encoders to work around
|
||||
bugs in Chrome's audio decoders.
|
||||
- Various clippy warnings.
|
||||
|
||||
### Changed
|
||||
- reqwest: Update to reqwest 0.12.
|
||||
- webrtchttp: Update to reqwest 0.12.
|
||||
|
||||
## [0.12.3] - 2024-03-21
|
||||
### Fixed
|
||||
- gtk4paintablesink: Fix scaling of texture position.
|
||||
- janusvrwebrtcsink: Handle 64 bit numerical room ids.
|
||||
- janusvrwebrtcsink: Don't include deprecated audio/video fields in publish
|
||||
messages.
|
||||
- janusvrwebrtcsink: Handle various other messages to avoid printing errors.
|
||||
- livekitwebrtc: Fix shutdown behaviour.
|
||||
- rtpgccbwe: Don't forward buffer lists with buffers from different SSRCs to
|
||||
avoid breaking assumptions in rtpsession.
|
||||
- sccparse: Ignore invalid timecodes during seeking.
|
||||
- webrtcsink: Don't try parsing audio caps as video caps.
|
||||
|
||||
### Changed
|
||||
- webrtc: Allow resolution and framerate changes.
|
||||
- webrtcsrc: Make producer-peer-id optional.
|
||||
|
||||
### Added
|
||||
- livekitwebrtcsrc: Add new LiveKit source element.
|
||||
- regex: Add support for configuring regex behaviour.
|
||||
- spotifyaudiosrc: Document how to use with non-Facebook accounts.
|
||||
- webrtcsrc: Add `do-retransmission` property.
|
||||
|
||||
## [0.12.2] - 2024-02-26
|
||||
### Fixed
|
||||
- rtpgccbwe: Don't reset PTS/DTS to `None` as otherwise `rtpsession` won't be
|
||||
able to generate valid RTCP.
|
||||
- webrtcsink: Fix usage with 1.22.
|
||||
|
||||
### Added
|
||||
- janusvrwebrtcsink: Add `secret-key` property.
|
||||
- janusvrwebrtcsink: Allow for string room ids and add `string-ids` property.
|
||||
- textwrap: Don't split on all whitespaces, especially not on non-breaking
|
||||
whitespace.
|
||||
|
||||
## [0.12.1] - 2024-02-13
|
||||
### Added
|
||||
- gtk4: Create a window for testing purposes when running in `gst-launch-1.0`
|
||||
or if `GST_GTK4_WINDOW=1` is set.
|
||||
- webrtcsink: Add `msid` property.
|
||||
|
||||
## [0.12.0] - 2024-02-08
|
||||
### Changed
|
||||
- ndi: `ndisrc` passes received data downstream without an additional copy, if
|
||||
|
@ -36,7 +228,6 @@ specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-v
|
|||
- New `janusvrwebrtcsink` element for the Janus VideoRoom API.
|
||||
- New `rtspsrc2` element.
|
||||
- New `whipserversrc` element.
|
||||
|
||||
- gtk4: New `background-color` property for setting the color of the
|
||||
background of the frame and the borders, if any.
|
||||
- gtk4: New `scale-filter` property for defining how to scale the frames.
|
||||
|
@ -344,7 +535,16 @@ specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-v
|
|||
- webrtcsink: Make the `turn-server` property a `turn-servers` list
|
||||
- webrtcsink: Move from async-std to tokio
|
||||
|
||||
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.0...HEAD
|
||||
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.13.1...HEAD
|
||||
[0.13.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.13.0...0.13.1
|
||||
[0.13.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.7...0.13.0
|
||||
[0.12.7]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.6...0.12.7
|
||||
[0.12.6]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.5...0.12.6
|
||||
[0.12.5]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.4...0.12.5
|
||||
[0.12.4]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.3...0.12.4
|
||||
[0.12.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.2...0.12.3
|
||||
[0.12.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.1...0.12.2
|
||||
[0.12.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.0...0.12.1
|
||||
[0.12.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.3...0.12.0
|
||||
[0.11.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.2...0.11.3
|
||||
[0.11.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.1...0.11.2
|
||||
|
|
3110
Cargo.lock
generated
3110
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
@ -9,12 +9,15 @@ members = [
|
|||
"audio/claxon",
|
||||
"audio/csound",
|
||||
"audio/lewton",
|
||||
"audio/speechmatics",
|
||||
"audio/spotify",
|
||||
|
||||
"generic/file",
|
||||
"generic/originalbuffer",
|
||||
"generic/sodium",
|
||||
"generic/threadshare",
|
||||
"generic/inter",
|
||||
"generic/gopbuffer",
|
||||
|
||||
"mux/flavors",
|
||||
"mux/fmp4",
|
||||
|
@ -22,6 +25,7 @@ members = [
|
|||
|
||||
"net/aws",
|
||||
"net/hlssink3",
|
||||
"net/mpegtslive",
|
||||
"net/ndi",
|
||||
"net/onvif",
|
||||
"net/raptorq",
|
||||
|
@ -32,6 +36,7 @@ members = [
|
|||
"net/webrtc",
|
||||
"net/webrtc/protocol",
|
||||
"net/webrtc/signalling",
|
||||
"net/quinn",
|
||||
|
||||
"text/ahead",
|
||||
"text/json",
|
||||
|
@ -65,13 +70,16 @@ default-members = [
|
|||
"audio/claxon",
|
||||
"audio/lewton",
|
||||
|
||||
"generic/originalbuffer",
|
||||
"generic/threadshare",
|
||||
"generic/inter",
|
||||
"generic/gopbuffer",
|
||||
|
||||
"mux/fmp4",
|
||||
"mux/mp4",
|
||||
|
||||
"net/aws",
|
||||
"net/mpegtslive",
|
||||
"net/hlssink3",
|
||||
"net/onvif",
|
||||
"net/raptorq",
|
||||
|
@ -83,6 +91,7 @@ default-members = [
|
|||
"net/webrtc/protocol",
|
||||
"net/webrtc/signalling",
|
||||
"net/ndi",
|
||||
"net/quinn",
|
||||
|
||||
"text/ahead",
|
||||
"text/json",
|
||||
|
@ -111,12 +120,13 @@ panic = 'unwind'
|
|||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
lto = "off"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.12.0-alpha.1"
|
||||
version = "0.14.0-alpha.1"
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
rust-version = "1.71"
|
||||
|
||||
[workspace.dependencies]
|
||||
once_cell = "1"
|
||||
|
@ -125,11 +135,12 @@ gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
|||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master", features=["use_glib"] }
|
||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||
gdk-wayland = { package = "gdk4-wayland", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||
gdk-x11 = { package = "gdk4-x11", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||
gdk-win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master", features = ["v4_6"]}
|
||||
gdk-wayland = { package = "gdk4-wayland", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master", features = ["v4_4"]}
|
||||
gdk-x11 = { package = "gdk4-x11", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master", features = ["v4_4"]}
|
||||
gdk-win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master", features = ["v4_4"]}
|
||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||
gst-allocators = { package = "gstreamer-allocators", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||
|
|
|
@ -23,6 +23,7 @@ You will find the following plugins in this repository:
|
|||
|
||||
- `aws`: Various elements for Amazon AWS services using the [AWS SDK](https://awslabs.github.io/aws-sdk-rust/) library
|
||||
- `s3src`/`s3sink`: A source and sink element to talk to the Amazon S3 object storage system.
|
||||
- `s3putobjectsink`: A sink element to talk to Amazon S3. Uses `PutObject` instead of multi-part upload like `s3sink`.
|
||||
- `s3hlssink`: A sink element to store HLS streams on Amazon S3.
|
||||
- `awstranscriber`: an element wrapping the AWS Transcriber service.
|
||||
- `awstranscribeparse`: an element parsing the packets of the AWS Transcriber service.
|
||||
|
@ -33,6 +34,9 @@ You will find the following plugins in this repository:
|
|||
|
||||
- `onvif`: Various elements for parsing, RTP (de)payloading, overlaying of ONVIF timed metadata.
|
||||
|
||||
- `quinn`: Transfer data over the network using QUIC
|
||||
- `quinnquicsink`/`quinnquicsrc`: Send and receive data using QUIC
|
||||
|
||||
- `raptorq`: Encoder/decoder element for RaptorQ RTP FEC mechanism.
|
||||
|
||||
- `reqwest`: An HTTP source element based on the [reqwest](https://github.com/seanmonstar/reqwest) library.
|
||||
|
|
|
@ -11,8 +11,8 @@ use gst::prelude::*;
|
|||
use gst::subclass::prelude::*;
|
||||
use gst_audio::subclass::prelude::*;
|
||||
|
||||
use std::cmp;
|
||||
use std::sync::Mutex;
|
||||
use std::{cmp, u64};
|
||||
|
||||
use byte_slice_cast::*;
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ use gst::subclass::prelude::*;
|
|||
|
||||
use std::mem;
|
||||
use std::sync::Mutex;
|
||||
use std::u64;
|
||||
|
||||
use byte_slice_cast::*;
|
||||
|
||||
|
@ -264,7 +263,7 @@ impl State {
|
|||
|
||||
// Drains everything
|
||||
fn drain(&mut self, imp: &AudioLoudNorm) -> Result<gst::Buffer, gst::FlowError> {
|
||||
gst::debug!(CAT, imp: imp, "Draining");
|
||||
gst::debug!(CAT, imp = imp, "Draining");
|
||||
|
||||
let (pts, distance) = self.adapter.prev_pts();
|
||||
let distance_samples = distance / self.info.bpf() as u64;
|
||||
|
@ -299,7 +298,7 @@ impl State {
|
|||
self.frame_type = FrameType::Final;
|
||||
} else if src.is_empty() {
|
||||
// Nothing to drain at all
|
||||
gst::debug!(CAT, imp: imp, "No data to drain");
|
||||
gst::debug!(CAT, imp = imp, "No data to drain");
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
|
||||
|
@ -342,7 +341,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Calculated global loudness for first frame {} with peak {}",
|
||||
global,
|
||||
true_peak
|
||||
|
@ -396,7 +395,7 @@ impl State {
|
|||
self.prev_delta = self.delta[self.index];
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Initializing for first frame with gain adjustment of {}",
|
||||
self.prev_delta
|
||||
);
|
||||
|
@ -458,7 +457,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Applying gain adjustment {}-{}",
|
||||
gain,
|
||||
gain_next
|
||||
|
@ -532,7 +531,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Calculated global loudness {}, short term loudness {} and relative threshold {}",
|
||||
global,
|
||||
shortterm,
|
||||
|
@ -555,7 +554,7 @@ impl State {
|
|||
self.above_threshold = true;
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Above threshold now ({} >= {}, {} > -70)",
|
||||
shortterm_out,
|
||||
self.target_i,
|
||||
|
@ -583,7 +582,7 @@ impl State {
|
|||
self.prev_delta = self.delta[self.index];
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Calculated new gain adjustment {}",
|
||||
self.prev_delta
|
||||
);
|
||||
|
@ -754,7 +753,7 @@ impl State {
|
|||
// amount of samples the last frame is short to reach the correct read position.
|
||||
if next_frame_size < FRAME_SIZE {
|
||||
self.limiter_buf_index += FRAME_SIZE - next_frame_size;
|
||||
if self.limiter_buf_index > self.limiter_buf.len() {
|
||||
if self.limiter_buf_index >= self.limiter_buf.len() {
|
||||
self.limiter_buf_index -= self.limiter_buf.len();
|
||||
}
|
||||
}
|
||||
|
@ -777,7 +776,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Applying linear gain adjustment of {}",
|
||||
self.offset
|
||||
);
|
||||
|
@ -856,7 +855,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found peak {} at sample {}, going to attack state at sample {} (gain reduction {}-{})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -993,7 +992,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new peak {} at sample {}, restarting attack state at sample {} (gain reduction {}-{})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1042,7 +1041,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new peak {} at sample {}, adjusting attack state at sample {} (gain reduction {}-{})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1057,7 +1056,7 @@ impl State {
|
|||
// to ensure that we at least sustain it for that long afterwards.
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new low peak {} at sample {} in attack state at sample {}",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1073,7 +1072,7 @@ impl State {
|
|||
// If we reached the target gain reduction, go into sustain state.
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Going to sustain state at sample {} (gain reduction {})",
|
||||
smp_cnt,
|
||||
self.gain_reduction[1]
|
||||
|
@ -1152,7 +1151,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1163,7 +1162,7 @@ impl State {
|
|||
} else {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new peak {} at sample {}, going sustain further at sample {} (gain reduction {})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1190,7 +1189,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Going to release state for sample {} at sample {} (gain reduction {}-1.0)",
|
||||
smp_cnt + LIMITER_RELEASE_WINDOW,
|
||||
smp_cnt,
|
||||
|
@ -1260,7 +1259,7 @@ impl State {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
||||
peak_value,
|
||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||
|
@ -1272,7 +1271,7 @@ impl State {
|
|||
self.gain_reduction[1] = current_gain_reduction;
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Going from release to sustain state at sample {} because of low peak {} at sample {} (gain reduction {})",
|
||||
smp_cnt,
|
||||
peak_value,
|
||||
|
@ -1313,7 +1312,7 @@ impl State {
|
|||
self.limiter_state = LimiterState::Out;
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Leaving release state and going to out state at sample {}",
|
||||
smp_cnt,
|
||||
);
|
||||
|
@ -1351,7 +1350,7 @@ impl State {
|
|||
self.gain_reduction[1] = self.target_tp / max;
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Reducing gain for start of first frame by {} ({} > {}) and going to sustain state",
|
||||
self.gain_reduction[1],
|
||||
max,
|
||||
|
@ -1367,7 +1366,7 @@ impl State {
|
|||
let channels = self.info.channels() as usize;
|
||||
let nb_samples = dst.len() / channels;
|
||||
|
||||
gst::debug!(CAT, imp: imp, "Running limiter for {} samples", nb_samples);
|
||||
gst::debug!(CAT, imp = imp, "Running limiter for {} samples", nb_samples);
|
||||
|
||||
// For the first frame we can't adjust the gain before it smoothly anymore so instead
|
||||
// apply the gain reduction immediately if we get above the threshold and move to sustain
|
||||
|
@ -1536,12 +1535,12 @@ impl AudioLoudNorm {
|
|||
_pad: &gst::Pad,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::log!(CAT, imp: self, "Handling buffer {:?}", buffer);
|
||||
gst::log!(CAT, imp = self, "Handling buffer {:?}", buffer);
|
||||
|
||||
let mut state_guard = self.state.borrow_mut();
|
||||
let state = match *state_guard {
|
||||
None => {
|
||||
gst::error!(CAT, imp: self, "Not negotiated yet");
|
||||
gst::error!(CAT, imp = self, "Not negotiated yet");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
Some(ref mut state) => state,
|
||||
|
@ -1549,7 +1548,7 @@ impl AudioLoudNorm {
|
|||
|
||||
let mut outbufs = vec![];
|
||||
if buffer.flags().contains(gst::BufferFlags::DISCONT) {
|
||||
gst::debug!(CAT, imp: self, "Draining on discontinuity");
|
||||
gst::debug!(CAT, imp = self, "Draining on discontinuity");
|
||||
match state.drain(self) {
|
||||
Ok(outbuf) => {
|
||||
outbufs.push(outbuf);
|
||||
|
@ -1567,7 +1566,7 @@ impl AudioLoudNorm {
|
|||
drop(state_guard);
|
||||
|
||||
for buffer in outbufs {
|
||||
gst::log!(CAT, imp: self, "Outputting buffer {:?}", buffer);
|
||||
gst::log!(CAT, imp = self, "Outputting buffer {:?}", buffer);
|
||||
self.srcpad.push(buffer)?;
|
||||
}
|
||||
|
||||
|
@ -1577,17 +1576,17 @@ impl AudioLoudNorm {
|
|||
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling event {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::Caps(c) => {
|
||||
let caps = c.caps();
|
||||
gst::info!(CAT, obj: pad, "Got caps {:?}", caps);
|
||||
gst::info!(CAT, obj = pad, "Got caps {:?}", caps);
|
||||
|
||||
let info = match gst_audio::AudioInfo::from_caps(caps) {
|
||||
Ok(info) => info,
|
||||
Err(_) => {
|
||||
gst::error!(CAT, obj: pad, "Failed to parse caps");
|
||||
gst::error!(CAT, obj = pad, "Failed to parse caps");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -1605,9 +1604,9 @@ impl AudioLoudNorm {
|
|||
drop(state);
|
||||
|
||||
if let Some(outbuf) = outbuf {
|
||||
gst::log!(CAT, imp: self, "Outputting buffer {:?}", outbuf);
|
||||
gst::log!(CAT, imp = self, "Outputting buffer {:?}", outbuf);
|
||||
if let Err(err) = self.srcpad.push(outbuf) {
|
||||
gst::error!(CAT, imp: self, "Failed to push drained data: {}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to push drained data: {}", err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1627,11 +1626,11 @@ impl AudioLoudNorm {
|
|||
drop(state);
|
||||
|
||||
if let Some(outbuf) = outbuf {
|
||||
gst::log!(CAT, imp: self, "Outputting buffer {:?}", outbuf);
|
||||
gst::log!(CAT, imp = self, "Outputting buffer {:?}", outbuf);
|
||||
if let Err(err) = self.srcpad.push(outbuf) {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to push drained data on EOS: {}",
|
||||
err
|
||||
);
|
||||
|
@ -1661,7 +1660,7 @@ impl AudioLoudNorm {
|
|||
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling query {:?}", query);
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Latency(q) => {
|
||||
let mut peer_query = gst::query::Latency::new();
|
||||
|
|
|
@ -113,7 +113,7 @@ impl AudioRNNoise {
|
|||
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
||||
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
||||
gst::error!(CAT, imp = self, "Failed to allocate buffer at EOS {:?}", e);
|
||||
gst::FlowError::Flushing
|
||||
})?;
|
||||
|
||||
|
@ -214,7 +214,7 @@ impl AudioRNNoise {
|
|||
);
|
||||
}
|
||||
|
||||
gst::trace!(CAT, imp: self, "Voice activity: {}", vad);
|
||||
gst::trace!(CAT, imp = self, "Voice activity: {}", vad);
|
||||
if vad < settings.vad_threshold {
|
||||
out_frame.fill(0.0);
|
||||
} else {
|
||||
|
@ -237,8 +237,9 @@ impl AudioRNNoise {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"rms: {}, level: {}, has_voice : {} ", rms,
|
||||
imp = self,
|
||||
"rms: {}, level: {}, has_voice : {} ",
|
||||
rms,
|
||||
level,
|
||||
has_voice
|
||||
);
|
||||
|
@ -345,7 +346,7 @@ impl BaseTransformImpl for AudioRNNoise {
|
|||
use gst::EventView;
|
||||
|
||||
if let EventView::Eos(_) = event.view() {
|
||||
gst::debug!(CAT, imp: self, "Handling EOS");
|
||||
gst::debug!(CAT, imp = self, "Handling EOS");
|
||||
if self.drain().is_err() {
|
||||
return false;
|
||||
}
|
||||
|
@ -361,7 +362,7 @@ impl BaseTransformImpl for AudioRNNoise {
|
|||
let (live, mut min, mut max) = upstream_query.result();
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Peer latency: live {} min {} max {}",
|
||||
live,
|
||||
min,
|
||||
|
@ -406,7 +407,7 @@ impl AudioFilterImpl for AudioRNNoise {
|
|||
})?;
|
||||
}
|
||||
|
||||
gst::debug!(CAT, imp: self, "Set caps to {:?}", info);
|
||||
gst::debug!(CAT, imp = self, "Set caps to {:?}", info);
|
||||
|
||||
let mut denoisers = vec![];
|
||||
for _i in 0..info.channels() {
|
||||
|
|
|
@ -12,7 +12,6 @@ use gst::subclass::prelude::*;
|
|||
use gst_audio::subclass::prelude::*;
|
||||
use gst_base::prelude::*;
|
||||
|
||||
use std::i32;
|
||||
use std::sync::atomic;
|
||||
use std::sync::Mutex;
|
||||
|
||||
|
@ -130,7 +129,7 @@ impl ObjectImpl for EbuR128Level {
|
|||
let this = args[0].get::<super::EbuR128Level>().unwrap();
|
||||
let imp = this.imp();
|
||||
|
||||
gst::info!(CAT, obj: this, "Resetting measurements",);
|
||||
gst::info!(CAT, obj = this, "Resetting measurements",);
|
||||
imp.reset.store(true, atomic::Ordering::SeqCst);
|
||||
|
||||
None
|
||||
|
@ -176,7 +175,7 @@ impl ObjectImpl for EbuR128Level {
|
|||
let mode = value.get().expect("type checked upstream");
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Changing mode from {:?} to {:?}",
|
||||
settings.mode,
|
||||
mode
|
||||
|
@ -187,7 +186,7 @@ impl ObjectImpl for EbuR128Level {
|
|||
let post_messages = value.get().expect("type checked upstream");
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Changing post-messages from {} to {}",
|
||||
settings.post_messages,
|
||||
post_messages
|
||||
|
@ -198,7 +197,7 @@ impl ObjectImpl for EbuR128Level {
|
|||
let interval = value.get::<u64>().unwrap().nseconds();
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Changing interval from {} to {}",
|
||||
settings.interval,
|
||||
interval,
|
||||
|
@ -287,7 +286,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
// Drop state
|
||||
let _ = self.state.borrow_mut().take();
|
||||
|
||||
gst::info!(CAT, imp: self, "Stopped");
|
||||
gst::info!(CAT, imp = self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -378,7 +377,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
Ok(loudness) => s.set("momentary-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to get momentary loudness: {}",
|
||||
err
|
||||
),
|
||||
|
@ -390,7 +389,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
Ok(loudness) => s.set("shortterm-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to get shortterm loudness: {}",
|
||||
err
|
||||
),
|
||||
|
@ -402,7 +401,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
Ok(loudness) => s.set("global-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to get global loudness: {}",
|
||||
err
|
||||
),
|
||||
|
@ -412,7 +411,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
Ok(threshold) => s.set("relative-threshold", threshold),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to get relative threshold: {}",
|
||||
err
|
||||
),
|
||||
|
@ -423,7 +422,12 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
match state.ebur128.loudness_range() {
|
||||
Ok(range) => s.set("loudness-range", range),
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to get loudness range: {}", err)
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp = self,
|
||||
"Failed to get loudness range: {}",
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -436,7 +440,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
match peaks {
|
||||
Ok(peaks) => s.set("sample-peak", peaks),
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to get sample peaks: {}", err)
|
||||
gst::error!(CAT, imp = self, "Failed to get sample peaks: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -449,12 +453,12 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
match peaks {
|
||||
Ok(peaks) => s.set("true-peak", peaks),
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to get true peaks: {}", err)
|
||||
gst::error!(CAT, imp = self, "Failed to get true peaks: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::debug!(CAT, imp: self, "Posting message {}", s);
|
||||
gst::debug!(CAT, imp = self, "Posting message {}", s);
|
||||
|
||||
let msg = gst::message::Element::builder(s).src(&*self.obj()).build();
|
||||
|
||||
|
@ -505,7 +509,7 @@ impl AudioFilterImpl for EbuR128Level {
|
|||
}
|
||||
|
||||
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||
gst::debug!(CAT, imp: self, "Configured for caps {:?}", info);
|
||||
gst::debug!(CAT, imp = self, "Configured for caps {:?}", info);
|
||||
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
|
||||
|
@ -568,7 +572,7 @@ impl AudioFilterImpl for EbuR128Level {
|
|||
val => {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Unknown channel position {:?}, ignoring channel",
|
||||
val
|
||||
);
|
||||
|
@ -746,12 +750,12 @@ fn interleaved_channel_data_into_slice<'a, T: FromByteSlice>(
|
|||
) -> Result<&'a [T], gst::FlowError> {
|
||||
buf.plane_data(0)
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp: imp, "Failed to get audio data: {}", err);
|
||||
gst::error!(CAT, imp = imp, "Failed to get audio data: {}", err);
|
||||
gst::FlowError::Error
|
||||
})?
|
||||
.as_slice_of::<T>()
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp: imp, "Failed to handle audio data: {}", err);
|
||||
gst::error!(CAT, imp = imp, "Failed to handle audio data: {}", err);
|
||||
gst::FlowError::Error
|
||||
})
|
||||
}
|
||||
|
@ -765,12 +769,12 @@ fn non_interleaved_channel_data_into_slices<'a, T: FromByteSlice>(
|
|||
.map(|c| {
|
||||
buf.plane_data(c)
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp: imp, "Failed to get audio data: {}", err);
|
||||
gst::error!(CAT, imp = imp, "Failed to get audio data: {}", err);
|
||||
gst::FlowError::Error
|
||||
})?
|
||||
.as_slice_of::<T>()
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp: imp, "Failed to handle audio data: {}", err);
|
||||
gst::error!(CAT, imp = imp, "Failed to handle audio data: {}", err);
|
||||
gst::FlowError::Error
|
||||
})
|
||||
})
|
||||
|
|
|
@ -224,7 +224,7 @@ impl HrtfRender {
|
|||
let mut outbuf =
|
||||
gst_audio::AudioBufferRef::from_buffer_ref_writable(outbuf, &state.outinfo).map_err(
|
||||
|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to map buffer : {}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to map buffer : {}", err);
|
||||
gst::FlowError::Error
|
||||
},
|
||||
)?;
|
||||
|
@ -248,13 +248,13 @@ impl HrtfRender {
|
|||
|
||||
while state.adapter.available() >= inblksz {
|
||||
let inbuf = state.adapter.take_buffer(inblksz).map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map buffer");
|
||||
gst::error!(CAT, imp = self, "Failed to map buffer");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let inbuf = gst_audio::AudioBuffer::from_buffer_readable(inbuf, &state.ininfo)
|
||||
.map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map buffer");
|
||||
gst::error!(CAT, imp = self, "Failed to map buffer");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
|
@ -624,7 +624,7 @@ impl BaseTransformImpl for HrtfRender {
|
|||
|
||||
gst::log!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Adapter size: {}, input size {}, transformed size {}",
|
||||
state.adapter.available(),
|
||||
size,
|
||||
|
@ -649,7 +649,7 @@ impl BaseTransformImpl for HrtfRender {
|
|||
|
||||
if direction == gst::PadDirection::Sink {
|
||||
s.set("channels", 2);
|
||||
s.set("channel-mask", 0x3);
|
||||
s.set("channel-mask", gst::Bitmask(0x3));
|
||||
} else {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
if let Some(objs) = &settings.spatial_objects {
|
||||
|
@ -670,7 +670,7 @@ impl BaseTransformImpl for HrtfRender {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Transformed caps from {} to {} in direction {:?}",
|
||||
caps,
|
||||
other_caps,
|
||||
|
@ -741,7 +741,7 @@ impl BaseTransformImpl for HrtfRender {
|
|||
adapter: gst_base::UniqueAdapter::new(),
|
||||
});
|
||||
|
||||
gst::debug!(CAT, imp: self, "Configured for caps {}", incaps);
|
||||
gst::debug!(CAT, imp = self, "Configured for caps {}", incaps);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -749,7 +749,7 @@ impl BaseTransformImpl for HrtfRender {
|
|||
fn sink_event(&self, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Handling event {:?}", event);
|
||||
gst::debug!(CAT, imp = self, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::FlushStop(_) => {
|
||||
|
|
|
@ -198,7 +198,7 @@ fn basic_two_channels() {
|
|||
|
||||
#[test]
|
||||
fn silence() {
|
||||
run_test("wave=silence", None, 1000, 1024, 1, std::f64::NEG_INFINITY);
|
||||
run_test("wave=silence", None, 1000, 1024, 1, f64::NEG_INFINITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -228,7 +228,7 @@ fn below_threshold() {
|
|||
1000,
|
||||
1024,
|
||||
1,
|
||||
std::f64::NEG_INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
}
|
||||
|
||||
fn set_format(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> {
|
||||
gst::debug!(CAT, imp: self, "Setting format {:?}", caps);
|
||||
gst::debug!(CAT, imp = self, "Setting format {:?}", caps);
|
||||
|
||||
let mut audio_info: Option<gst_audio::AudioInfo> = None;
|
||||
|
||||
|
@ -124,15 +124,15 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
let streamheaders = streamheaders.as_slice();
|
||||
|
||||
if streamheaders.len() < 2 {
|
||||
gst::debug!(CAT, imp: self, "Not enough streamheaders, trying in-band");
|
||||
gst::debug!(CAT, imp = self, "Not enough streamheaders, trying in-band");
|
||||
} else {
|
||||
let ident_buf = streamheaders[0].get::<Option<gst::Buffer>>();
|
||||
if let Ok(Some(ident_buf)) = ident_buf {
|
||||
gst::debug!(CAT, imp: self, "Got streamheader buffers");
|
||||
gst::debug!(CAT, imp = self, "Got streamheader buffers");
|
||||
let inmap = ident_buf.map_readable().unwrap();
|
||||
|
||||
if inmap[0..7] != [0x7f, b'F', b'L', b'A', b'C', 0x01, 0x00] {
|
||||
gst::debug!(CAT, imp: self, "Unknown streamheader format");
|
||||
gst::debug!(CAT, imp = self, "Unknown streamheader format");
|
||||
} else if let Ok(tstreaminfo) = claxon_streaminfo(&inmap[13..]) {
|
||||
if let Ok(taudio_info) = gstaudioinfo(&tstreaminfo) {
|
||||
// To speed up negotiation
|
||||
|
@ -142,7 +142,7 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
{
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Error to negotiate output from based on in-caps streaminfo"
|
||||
);
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
&self,
|
||||
inbuf: Option<&gst::Buffer>,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::debug!(CAT, imp: self, "Handling buffer {:?}", inbuf);
|
||||
gst::debug!(CAT, imp = self, "Handling buffer {:?}", inbuf);
|
||||
|
||||
let inbuf = match inbuf {
|
||||
None => return Ok(gst::FlowSuccess::Ok),
|
||||
|
@ -173,7 +173,7 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
};
|
||||
|
||||
let inmap = inbuf.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to buffer readable");
|
||||
gst::error!(CAT, imp = self, "Failed to buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
|
@ -181,18 +181,18 @@ impl AudioDecoderImpl for ClaxonDec {
|
|||
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
||||
|
||||
if inmap.as_slice() == b"fLaC" {
|
||||
gst::debug!(CAT, imp: self, "fLaC buffer received");
|
||||
gst::debug!(CAT, imp = self, "fLaC buffer received");
|
||||
} else if inmap[0] & 0x7F == 0x00 {
|
||||
gst::debug!(CAT, imp: self, "Streaminfo header buffer received");
|
||||
gst::debug!(CAT, imp = self, "Streaminfo header buffer received");
|
||||
return self.handle_streaminfo_header(state, inmap.as_ref());
|
||||
} else if inmap[0] == 0b1111_1111 && inmap[1] & 0b1111_1100 == 0b1111_1000 {
|
||||
gst::debug!(CAT, imp: self, "Data buffer received");
|
||||
gst::debug!(CAT, imp = self, "Data buffer received");
|
||||
return self.handle_data(state, inmap.as_ref());
|
||||
} else {
|
||||
// info about other headers in flacparse and https://xiph.org/flac/format.html
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Other header buffer received {:?}",
|
||||
inmap[0] & 0x7F
|
||||
);
|
||||
|
@ -220,7 +220,7 @@ impl ClaxonDec {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Successfully parsed headers: {:?}",
|
||||
audio_info
|
||||
);
|
||||
|
|
|
@ -17,7 +17,6 @@ use gst_base::subclass::prelude::*;
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::{f64, i32};
|
||||
|
||||
use byte_slice_cast::*;
|
||||
|
||||
|
@ -192,7 +191,7 @@ impl CsoundFilter {
|
|||
(avail / state.in_info.channels() as usize) * state.out_info.channels() as usize;
|
||||
|
||||
let mut buffer = gst::Buffer::with_size(out_bytes).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
|
||||
})?;
|
||||
|
||||
|
@ -247,7 +246,7 @@ impl CsoundFilter {
|
|||
|
||||
gst::log!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Generating output at: {} - duration: {}",
|
||||
pts.display(),
|
||||
duration.display(),
|
||||
|
@ -482,7 +481,7 @@ impl BaseTransformImpl for CsoundFilter {
|
|||
csound.reset();
|
||||
let _ = self.state.lock().unwrap().take();
|
||||
|
||||
gst::info!(CAT, imp: self, "Stopped");
|
||||
gst::info!(CAT, imp = self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -491,7 +490,7 @@ impl BaseTransformImpl for CsoundFilter {
|
|||
use gst::EventView;
|
||||
|
||||
if let EventView::Eos(_) = event.view() {
|
||||
gst::log!(CAT, imp: self, "Handling Eos");
|
||||
gst::log!(CAT, imp = self, "Handling Eos");
|
||||
if self.drain().is_err() {
|
||||
return false;
|
||||
}
|
||||
|
@ -536,7 +535,7 @@ impl BaseTransformImpl for CsoundFilter {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Transformed caps from {} to {} in direction {:?}",
|
||||
caps,
|
||||
other_caps,
|
||||
|
|
|
@ -120,7 +120,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
}
|
||||
|
||||
fn set_format(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> {
|
||||
gst::debug!(CAT, imp: self, "Setting format {:?}", caps);
|
||||
gst::debug!(CAT, imp = self, "Setting format {:?}", caps);
|
||||
|
||||
// When the caps are changing we require new headers
|
||||
let mut state_guard = self.state.borrow_mut();
|
||||
|
@ -138,7 +138,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
if let Ok(Some(streamheaders)) = s.get_optional::<gst::ArrayRef>("streamheader") {
|
||||
let streamheaders = streamheaders.as_slice();
|
||||
if streamheaders.len() < 3 {
|
||||
gst::debug!(CAT, imp: self, "Not enough streamheaders, trying in-band");
|
||||
gst::debug!(CAT, imp = self, "Not enough streamheaders, trying in-band");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
if let (Ok(Some(ident_buf)), Ok(Some(comment_buf)), Ok(Some(setup_buf))) =
|
||||
(ident_buf, comment_buf, setup_buf)
|
||||
{
|
||||
gst::debug!(CAT, imp: self, "Got streamheader buffers");
|
||||
gst::debug!(CAT, imp = self, "Got streamheader buffers");
|
||||
state.header_bufs = (Some(ident_buf), Some(comment_buf), Some(setup_buf));
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
}
|
||||
|
||||
fn flush(&self, _hard: bool) {
|
||||
gst::debug!(CAT, imp: self, "Flushing");
|
||||
gst::debug!(CAT, imp = self, "Flushing");
|
||||
|
||||
let mut state_guard = self.state.borrow_mut();
|
||||
if let Some(ref mut state) = *state_guard {
|
||||
|
@ -169,7 +169,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
&self,
|
||||
inbuf: Option<&gst::Buffer>,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::debug!(CAT, imp: self, "Handling buffer {:?}", inbuf);
|
||||
gst::debug!(CAT, imp = self, "Handling buffer {:?}", inbuf);
|
||||
|
||||
let inbuf = match inbuf {
|
||||
None => return Ok(gst::FlowSuccess::Ok),
|
||||
|
@ -177,7 +177,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
};
|
||||
|
||||
let inmap = inbuf.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to buffer readable");
|
||||
gst::error!(CAT, imp = self, "Failed to buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
|
@ -191,7 +191,7 @@ impl AudioDecoderImpl for LewtonDec {
|
|||
if state.headerset.is_some() {
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
} else {
|
||||
gst::error!(CAT, imp: self, "Got empty packet before all headers");
|
||||
gst::error!(CAT, imp = self, "Got empty packet before all headers");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
}
|
||||
|
@ -219,14 +219,14 @@ impl LewtonDec {
|
|||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
// ident header
|
||||
if indata[0] == 0x01 {
|
||||
gst::debug!(CAT, imp: self, "Got ident header buffer");
|
||||
gst::debug!(CAT, imp = self, "Got ident header buffer");
|
||||
state.header_bufs = (Some(inbuf.clone()), None, None);
|
||||
} else if indata[0] == 0x03 {
|
||||
// comment header
|
||||
if state.header_bufs.0.is_none() {
|
||||
gst::warning!(CAT, imp: self, "Got comment header before ident header");
|
||||
gst::warning!(CAT, imp = self, "Got comment header before ident header");
|
||||
} else {
|
||||
gst::debug!(CAT, imp: self, "Got comment header buffer");
|
||||
gst::debug!(CAT, imp = self, "Got comment header buffer");
|
||||
state.header_bufs.1 = Some(inbuf.clone());
|
||||
}
|
||||
} else if indata[0] == 0x05 {
|
||||
|
@ -234,11 +234,11 @@ impl LewtonDec {
|
|||
if state.header_bufs.0.is_none() || state.header_bufs.1.is_none() {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Got setup header before ident/comment header"
|
||||
);
|
||||
} else {
|
||||
gst::debug!(CAT, imp: self, "Got setup header buffer");
|
||||
gst::debug!(CAT, imp = self, "Got setup header buffer");
|
||||
state.header_bufs.2 = Some(inbuf.clone());
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ impl LewtonDec {
|
|||
|
||||
// First try to parse the headers
|
||||
let ident_map = ident_buf.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map ident buffer readable");
|
||||
gst::error!(CAT, imp = self, "Failed to map ident buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
let ident = lewton::header::read_header_ident(ident_map.as_ref()).map_err(|err| {
|
||||
|
@ -276,7 +276,7 @@ impl LewtonDec {
|
|||
})?;
|
||||
|
||||
let comment_map = comment_buf.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map comment buffer readable");
|
||||
gst::error!(CAT, imp = self, "Failed to map comment buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
let comment = lewton::header::read_header_comment(comment_map.as_ref()).map_err(|err| {
|
||||
|
@ -289,7 +289,7 @@ impl LewtonDec {
|
|||
})?;
|
||||
|
||||
let setup_map = setup_buf.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Failed to map setup buffer readable");
|
||||
gst::error!(CAT, imp = self, "Failed to map setup buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
let setup = lewton::header::read_header_setup(
|
||||
|
@ -327,7 +327,7 @@ impl LewtonDec {
|
|||
if gst_audio::channel_reorder_map(from, to, &mut map[..channels]).is_err() {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to generate channel reorder map from {:?} to {:?}",
|
||||
from,
|
||||
to,
|
||||
|
@ -343,7 +343,7 @@ impl LewtonDec {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Successfully parsed headers: {:?}",
|
||||
audio_info
|
||||
);
|
||||
|
@ -396,7 +396,7 @@ impl LewtonDec {
|
|||
}
|
||||
|
||||
let sample_count = decoded.samples.len() / audio_info.channels() as usize;
|
||||
gst::debug!(CAT, imp: self, "Got {} decoded samples", sample_count);
|
||||
gst::debug!(CAT, imp = self, "Got {} decoded samples", sample_count);
|
||||
|
||||
if sample_count == 0 {
|
||||
return self.obj().finish_frame(None, 1);
|
||||
|
|
50
audio/speechmatics/Cargo.toml
Normal file
50
audio/speechmatics/Cargo.toml
Normal file
|
@ -0,0 +1,50 @@
|
|||
[package]
|
||||
name = "gst-plugin-speechmatics"
|
||||
version.workspace = true
|
||||
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||
repository.workspace = true
|
||||
license = "MPL-2.0"
|
||||
description = "GStreamer Speechmatics plugin"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
gst.workspace = true
|
||||
gst-base.workspace = true
|
||||
gst-audio = { workspace = true, features = ["v1_16"] }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
async-tungstenite = { version = "0.28", features = ["tokio", "tokio-runtime", "tokio-native-tls"] }
|
||||
once_cell.workspace = true
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
atomic_refcell = "0.1"
|
||||
http = { version = "1.0" }
|
||||
url = "2"
|
||||
|
||||
[lib]
|
||||
name = "gstspeechmatics"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper.workspace = true
|
||||
|
||||
[features]
|
||||
static = []
|
||||
capi = []
|
||||
doc = ["gst/v1_18"]
|
||||
|
||||
[package.metadata.capi]
|
||||
min_version = "0.9.21"
|
||||
|
||||
[package.metadata.capi.header]
|
||||
enabled = false
|
||||
|
||||
[package.metadata.capi.library]
|
||||
install_subdir = "gstreamer-1.0"
|
||||
versioning = false
|
||||
import_library = false
|
||||
|
||||
[package.metadata.capi.pkg_config]
|
||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
373
audio/speechmatics/LICENSE-MPL-2.0
Normal file
373
audio/speechmatics/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
3
audio/speechmatics/build.rs
Normal file
3
audio/speechmatics/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
gst_plugin_version_helper::info()
|
||||
}
|
36
audio/speechmatics/src/lib.rs
Normal file
36
audio/speechmatics/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (C) 2024 Mathieu Duponchelle <mathieu@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)]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
/**
|
||||
* plugin-speechmatics:
|
||||
*
|
||||
* Since: plugins-rs-0.14.0
|
||||
*/
|
||||
use gst::glib;
|
||||
|
||||
mod transcriber;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
transcriber::register(plugin)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
gst::plugin_define!(
|
||||
speechmatics,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||
"Proprietary",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_REPOSITORY"),
|
||||
env!("BUILD_REL_DATE")
|
||||
);
|
1778
audio/speechmatics/src/transcriber/imp.rs
Normal file
1778
audio/speechmatics/src/transcriber/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
33
audio/speechmatics/src/transcriber/mod.rs
Normal file
33
audio/speechmatics/src/transcriber/mod.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (C) 2024 Mathieu Duponchelle <mathieu@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 imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Transcriber(ObjectSubclass<imp::Transcriber>) @extends gst::Element, gst::Object, @implements gst::ChildProxy;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TranscriberSrcPad(ObjectSubclass<imp::TranscriberSrcPad>) @extends gst::Pad, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
TranscriberSrcPad::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"speechmaticstranscriber",
|
||||
gst::Rank::NONE,
|
||||
Transcriber::static_type(),
|
||||
)
|
||||
}
|
|
@ -11,8 +11,9 @@ rust-version.workspace = true
|
|||
[dependencies]
|
||||
gst.workspace = true
|
||||
gst-base.workspace = true
|
||||
librespot = { version = "0.4", default-features = false }
|
||||
tokio = "1.0"
|
||||
librespot-core = "0.4"
|
||||
librespot-playback = "0.4"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
futures = "0.3"
|
||||
anyhow = "1.0"
|
||||
url = "2.3"
|
||||
|
|
|
@ -8,10 +8,11 @@ to respect their legal/licensing restrictions.
|
|||
|
||||
## Spotify Credentials
|
||||
|
||||
This plugin requires a [Spotify Premium](https://www.spotify.com/premium/) account configured
|
||||
with a [device password](https://www.spotify.com/us/account/set-device-password/).
|
||||
This plugin requires a [Spotify Premium](https://www.spotify.com/premium/) account.
|
||||
If your account is linked with Facebook, you'll need to setup
|
||||
a [device username and password](https://www.spotify.com/us/account/set-device-password/).
|
||||
|
||||
You can then set the device username and password using the `username` and `password` properties.
|
||||
Those username and password are then set using the `username` and `password` properties.
|
||||
|
||||
You may also want to cache credentials and downloaded files, see the `cache-` properties on the element.
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ use anyhow::bail;
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
use librespot::core::{
|
||||
cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId,
|
||||
use librespot_core::{
|
||||
authentication::Credentials, cache::Cache, config::SessionConfig, session::Session,
|
||||
spotify_id::SpotifyId,
|
||||
};
|
||||
use librespot::discovery::Credentials;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Settings {
|
||||
|
@ -30,13 +30,13 @@ impl Settings {
|
|||
pub fn properties() -> Vec<glib::ParamSpec> {
|
||||
vec![glib::ParamSpecString::builder("username")
|
||||
.nick("Username")
|
||||
.blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/")
|
||||
.blurb("Spotify username, Facebook accounts need a device username from https://www.spotify.com/us/account/set-device-password/")
|
||||
.default_value(Some(""))
|
||||
.mutable_ready()
|
||||
.build(),
|
||||
glib::ParamSpecString::builder("password")
|
||||
.nick("Password")
|
||||
.blurb("Spotify device password from https://www.spotify.com/us/account/set-device-password/")
|
||||
.blurb("Spotify password, Facebook accounts need a device password from https://www.spotify.com/us/account/set-device-password/")
|
||||
.default_value(Some(""))
|
||||
.mutable_ready()
|
||||
.build(),
|
||||
|
@ -135,7 +135,7 @@ impl Settings {
|
|||
if !self.username.is_empty() && self.username != cached_cred.username {
|
||||
gst::debug!(
|
||||
cat,
|
||||
obj: &src,
|
||||
obj = &src,
|
||||
"ignore cached credentials for user {} which mismatch user {}",
|
||||
cached_cred.username,
|
||||
self.username
|
||||
|
@ -143,7 +143,7 @@ impl Settings {
|
|||
} else {
|
||||
gst::debug!(
|
||||
cat,
|
||||
obj: &src,
|
||||
obj = &src,
|
||||
"reuse cached credentials for user {}",
|
||||
cached_cred.username
|
||||
);
|
||||
|
@ -162,7 +162,7 @@ impl Settings {
|
|||
|
||||
gst::debug!(
|
||||
cat,
|
||||
obj: &src,
|
||||
obj = &src,
|
||||
"credentials not in cache or cached credentials invalid",
|
||||
);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -17,7 +17,7 @@ use gst::prelude::*;
|
|||
use gst::subclass::prelude::*;
|
||||
use gst_base::subclass::{base_src::CreateSuccess, prelude::*};
|
||||
|
||||
use librespot::playback::{
|
||||
use librespot_playback::{
|
||||
audio_backend::{Sink, SinkResult},
|
||||
config::PlayerConfig,
|
||||
convert::Converter,
|
||||
|
@ -67,15 +67,39 @@ struct Settings {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpotifyAudioSrc {
|
||||
setup_thread: Mutex<Option<SetupThread>>,
|
||||
state: Arc<Mutex<Option<State>>>,
|
||||
settings: Mutex<Settings>,
|
||||
enum SetupThread {
|
||||
#[default]
|
||||
None,
|
||||
Pending {
|
||||
thread_handle: Option<std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>>,
|
||||
abort_handle: AbortHandle,
|
||||
},
|
||||
Cancelled,
|
||||
Done,
|
||||
}
|
||||
|
||||
struct SetupThread {
|
||||
thread_handle: std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>,
|
||||
abort_handle: AbortHandle,
|
||||
impl SetupThread {
|
||||
fn abort(&mut self) {
|
||||
// Cancel setup thread if it is pending and not done yet
|
||||
if matches!(self, SetupThread::None | SetupThread::Done) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let SetupThread::Pending {
|
||||
ref abort_handle, ..
|
||||
} = *self
|
||||
{
|
||||
abort_handle.abort();
|
||||
}
|
||||
*self = SetupThread::Cancelled;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpotifyAudioSrc {
|
||||
setup_thread: Mutex<SetupThread>,
|
||||
state: Arc<Mutex<Option<State>>>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -172,23 +196,20 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
|||
}
|
||||
|
||||
{
|
||||
let setup_thread = self.setup_thread.lock().unwrap();
|
||||
if setup_thread.is_some() {
|
||||
// already starting
|
||||
return Ok(());
|
||||
// If not started yet and not cancelled, start the setup
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
assert!(!matches!(&*setup_thread, SetupThread::Cancelled));
|
||||
if matches!(&*setup_thread, SetupThread::None) {
|
||||
self.start_setup(&mut setup_thread);
|
||||
}
|
||||
self.start_setup(setup_thread);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
// stop the setup if it's not completed yet
|
||||
self.cancel_setup();
|
||||
|
||||
if let Some(state) = self.state.lock().unwrap().take() {
|
||||
gst::debug!(CAT, imp: self, "stopping");
|
||||
gst::debug!(CAT, imp = self, "stopping");
|
||||
state.player.stop();
|
||||
state.player_channel_handle.abort();
|
||||
// FIXME: not sure why this is needed to unblock BufferSink::write(), dropping State should drop the receiver
|
||||
|
@ -199,9 +220,17 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
|||
}
|
||||
|
||||
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.cancel_setup();
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
setup_thread.abort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
self.parent_unlock()
|
||||
fn unlock_stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
if matches!(&*setup_thread, SetupThread::Cancelled) {
|
||||
*setup_thread = SetupThread::None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,30 +245,47 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
|||
};
|
||||
|
||||
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);
|
||||
// If not started yet and not cancelled, start the setup
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
if matches!(&*setup_thread, SetupThread::Cancelled) {
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
if matches!(&*setup_thread, SetupThread::None) {
|
||||
self.start_setup(&mut 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();
|
||||
if let SetupThread::Pending {
|
||||
ref mut thread_handle,
|
||||
..
|
||||
} = *setup_thread
|
||||
{
|
||||
let thread_handle = thread_handle.take().expect("Waiting multiple times");
|
||||
drop(setup_thread);
|
||||
let res = thread_handle.join().unwrap();
|
||||
|
||||
match res {
|
||||
Err(_aborted) => {
|
||||
gst::debug!(CAT, imp: self, "setup has been cancelled");
|
||||
gst::debug!(CAT, imp = self, "setup has been cancelled");
|
||||
setup_thread = self.setup_thread.lock().unwrap();
|
||||
*setup_thread = SetupThread::Cancelled;
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
gst::error!(CAT, imp: self, "failed to start: {err:?}");
|
||||
gst::error!(CAT, imp = self, "failed to start: {err:?}");
|
||||
gst::element_imp_error!(self, gst::ResourceError::Settings, ["{err:?}"]);
|
||||
setup_thread = self.setup_thread.lock().unwrap();
|
||||
*setup_thread = SetupThread::None;
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
Ok(Ok(_)) => {}
|
||||
Ok(Ok(_)) => {
|
||||
setup_thread = self.setup_thread.lock().unwrap();
|
||||
*setup_thread = SetupThread::Done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,15 +295,15 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
|||
|
||||
match state.receiver.recv().unwrap() {
|
||||
Message::Buffer(buffer) => {
|
||||
gst::log!(CAT, imp: self, "got buffer of size {}", buffer.size());
|
||||
gst::log!(CAT, imp = self, "got buffer of size {}", buffer.size());
|
||||
Ok(CreateSuccess::NewBuffer(buffer))
|
||||
}
|
||||
Message::Eos => {
|
||||
gst::debug!(CAT, imp: self, "eos");
|
||||
gst::debug!(CAT, imp = self, "eos");
|
||||
Err(gst::FlowError::Eos)
|
||||
}
|
||||
Message::Unavailable => {
|
||||
gst::error!(CAT, imp: self, "track is not available");
|
||||
gst::error!(CAT, imp = self, "track is not available");
|
||||
gst::element_imp_error!(
|
||||
self,
|
||||
gst::ResourceError::NotFound,
|
||||
|
@ -306,7 +352,7 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
|||
}
|
||||
|
||||
fn set_uri(&self, uri: &str) -> Result<(), glib::Error> {
|
||||
gst::debug!(CAT, imp: self, "set URI: {}", uri);
|
||||
gst::debug!(CAT, imp = self, "set URI: {}", uri);
|
||||
|
||||
let url = url::Url::parse(uri)
|
||||
.map_err(|e| glib::Error::new(gst::URIError::BadUri, &format!("{e:?}")))?;
|
||||
|
@ -318,7 +364,7 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
|||
self.obj().set_property(&key, value.as_ref());
|
||||
}
|
||||
_ => {
|
||||
gst::warning!(CAT, imp: self, "unsupported query: {}={}", key, value);
|
||||
gst::warning!(CAT, imp = self, "unsupported query: {}={}", key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +377,9 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
|||
}
|
||||
|
||||
impl SpotifyAudioSrc {
|
||||
fn start_setup(&self, mut setup_thread: MutexGuard<Option<SetupThread>>) {
|
||||
fn start_setup(&self, setup_thread: &mut SetupThread) {
|
||||
assert!(matches!(setup_thread, SetupThread::None));
|
||||
|
||||
let self_ = self.to_owned();
|
||||
|
||||
// run the runtime from another thread to prevent the "start a runtime from within a runtime" panic
|
||||
|
@ -344,10 +392,10 @@ impl SpotifyAudioSrc {
|
|||
})
|
||||
});
|
||||
|
||||
setup_thread.replace(SetupThread {
|
||||
thread_handle,
|
||||
*setup_thread = SetupThread::Pending {
|
||||
thread_handle: Some(thread_handle),
|
||||
abort_handle,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async fn setup(&self) -> anyhow::Result<()> {
|
||||
|
@ -372,7 +420,7 @@ impl SpotifyAudioSrc {
|
|||
|
||||
let session = common.connect_session(src.clone(), &CAT).await?;
|
||||
let track = common.track_id()?;
|
||||
gst::debug!(CAT, imp: self, "Requesting bitrate {:?}", bitrate);
|
||||
gst::debug!(CAT, imp = self, "Requesting bitrate {:?}", bitrate);
|
||||
|
||||
(session, track, bitrate)
|
||||
};
|
||||
|
@ -420,12 +468,4 @@ impl SpotifyAudioSrc {
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Default for Bitrate {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Bitrate> for librespot::playback::config::Bitrate {
|
||||
impl From<Bitrate> for librespot_playback::config::Bitrate {
|
||||
fn from(value: Bitrate) -> Self {
|
||||
match value {
|
||||
Bitrate::B96 => Self::Bitrate96,
|
||||
|
|
103
ci/cerbero/trigger_cerbero_pipeline.py
Executable file
103
ci/cerbero/trigger_cerbero_pipeline.py
Executable file
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/python3
|
||||
#
|
||||
# Copied from gstreamer.git/ci/gitlab/trigger_cerbero_pipeline.py
|
||||
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import gitlab
|
||||
|
||||
CERBERO_PROJECT = 'gstreamer/cerbero'
|
||||
|
||||
|
||||
class Status:
|
||||
FAILED = 'failed'
|
||||
MANUAL = 'manual'
|
||||
CANCELED = 'canceled'
|
||||
SUCCESS = 'success'
|
||||
SKIPPED = 'skipped'
|
||||
CREATED = 'created'
|
||||
|
||||
@classmethod
|
||||
def is_finished(cls, state):
|
||||
return state in [
|
||||
cls.FAILED,
|
||||
cls.MANUAL,
|
||||
cls.CANCELED,
|
||||
cls.SUCCESS,
|
||||
cls.SKIPPED,
|
||||
]
|
||||
|
||||
|
||||
def fprint(msg):
|
||||
print(msg, end="")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = os.environ['CI_SERVER_URL']
|
||||
gl = gitlab.Gitlab(server,
|
||||
private_token=os.environ.get('GITLAB_API_TOKEN'),
|
||||
job_token=os.environ.get('CI_JOB_TOKEN'))
|
||||
|
||||
def get_matching_user_project(project, branch):
|
||||
cerbero = gl.projects.get(project)
|
||||
# Search for matching branches, return only if the branch name matches
|
||||
# exactly
|
||||
for b in cerbero.branches.list(search=cerbero_branch, iterator=True):
|
||||
if branch == b.name:
|
||||
return cerbero
|
||||
return None
|
||||
|
||||
cerbero = None
|
||||
# We do not want to run on (often out of date) user upstream branch
|
||||
if os.environ["CI_COMMIT_REF_NAME"] != os.environ['CERBERO_UPSTREAM_BRANCH']:
|
||||
try:
|
||||
cerbero_name = f'{os.environ["CI_PROJECT_NAMESPACE"]}/cerbero'
|
||||
cerbero_branch = os.environ["CI_COMMIT_REF_NAME"]
|
||||
cerbero = get_matching_user_project(cerbero_name, cerbero_branch)
|
||||
except gitlab.exceptions.GitlabGetError:
|
||||
pass
|
||||
|
||||
if cerbero is None:
|
||||
cerbero_name = CERBERO_PROJECT
|
||||
cerbero_branch = os.environ["CERBERO_UPSTREAM_BRANCH"]
|
||||
cerbero = gl.projects.get(cerbero_name)
|
||||
|
||||
fprint(f"-> Triggering on branch {cerbero_branch} in {cerbero_name}\n")
|
||||
|
||||
# CI_PROJECT_URL is not necessarily the project where the branch we need to
|
||||
# build resides, for instance merge request pipelines can be run on
|
||||
# 'gstreamer' namespace. Fetch the branch name in the same way, just in
|
||||
# case it breaks in the future.
|
||||
if 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' in os.environ:
|
||||
project_url = os.environ['CI_MERGE_REQUEST_SOURCE_PROJECT_URL']
|
||||
project_branch = os.environ['CI_MERGE_REQUEST_SOURCE_BRANCH_NAME']
|
||||
else:
|
||||
project_url = os.environ['CI_PROJECT_URL']
|
||||
project_branch = os.environ['CI_COMMIT_REF_NAME']
|
||||
|
||||
variables = {
|
||||
"CI_GST_PLUGINS_RS_URL": project_url,
|
||||
"CI_GST_PLUGINS_RS_REF_NAME": project_branch,
|
||||
# This tells cerbero CI that this is a pipeline started via the
|
||||
# trigger API, which means it can use a deps cache instead of
|
||||
# building from scratch.
|
||||
"CI_GSTREAMER_TRIGGERED": "true",
|
||||
}
|
||||
|
||||
pipe = cerbero.trigger_pipeline(
|
||||
token=os.environ['CI_JOB_TOKEN'],
|
||||
ref=cerbero_branch,
|
||||
variables=variables,
|
||||
)
|
||||
|
||||
fprint(f'Cerbero pipeline running at {pipe.web_url} ')
|
||||
while True:
|
||||
time.sleep(15)
|
||||
pipe.refresh()
|
||||
if Status.is_finished(pipe.status):
|
||||
fprint(f": {pipe.status}\n")
|
||||
sys.exit(0 if pipe.status == Status.SUCCESS else 1)
|
||||
else:
|
||||
fprint(".")
|
14
ci/check-meson-version.sh
Executable file
14
ci/check-meson-version.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
MESON_VERSION=`head -n5 meson.build | grep ' version\s*:' | sed -e "s/.*version\s*:\s*'//" -e "s/',.*//"`
|
||||
CARGO_VERSION=`cat Cargo.toml | grep -A1 workspace.package | grep ^version | sed -e 's/^version = "\(.*\)"/\1/'`
|
||||
|
||||
echo "gst-plugins-rs version (meson.build) : $MESON_VERSION"
|
||||
echo "gst-plugins-rs version (Cargo.toml) : $CARGO_VERSION"
|
||||
|
||||
if test "x$MESON_VERSION" != "x$CARGO_VERSION"; then
|
||||
echo
|
||||
echo "===> Version mismatch between meson.build and Cargo.toml! <==="
|
||||
echo
|
||||
exit 1;
|
||||
fi
|
|
@ -1,11 +0,0 @@
|
|||
set -e
|
||||
|
||||
RELEASE=1.1.0
|
||||
|
||||
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
|
||||
cd dav1d
|
||||
meson build -D prefix=/usr/local
|
||||
ninja -C build
|
||||
ninja -C build install
|
||||
cd ..
|
||||
rm -rf dav1d
|
|
@ -1,6 +0,0 @@
|
|||
source ./ci/env.sh
|
||||
|
||||
set -e
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
cargo install cargo-c --version 0.9.15+cargo-0.67
|
|
@ -36,6 +36,7 @@ function Run-Tests {
|
|||
}
|
||||
|
||||
$env:G_DEBUG="fatal_warnings"
|
||||
$env:RUST_BACKTRACE="1"
|
||||
cargo test --no-fail-fast --color=always --workspace $local_exclude --all-targets $Features
|
||||
|
||||
if (!$?) {
|
||||
|
|
152
deny.toml
152
deny.toml
|
@ -1,9 +1,7 @@
|
|||
[advisories]
|
||||
version = 2
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
vulnerability = "deny"
|
||||
unmaintained = "warn"
|
||||
notice = "warn"
|
||||
ignore = [
|
||||
# Waiting for https://github.com/librespot-org/librespot/issues/937
|
||||
"RUSTSEC-2021-0059",
|
||||
|
@ -11,17 +9,27 @@ ignore = [
|
|||
"RUSTSEC-2021-0061",
|
||||
"RUSTSEC-2021-0145",
|
||||
# sodiumoxide is deprecated
|
||||
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/530
|
||||
"RUSTSEC-2021-0137",
|
||||
# proc-macro-error is unmaintained
|
||||
# https://github.com/yanganto/test-with/issues/91
|
||||
"RUSTSEC-2024-0370",
|
||||
]
|
||||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
version = 2
|
||||
allow = [
|
||||
"MIT",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"OpenSSL",
|
||||
"Zlib",
|
||||
"Unicode-DFS-2016",
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"MPL-2.0",
|
||||
]
|
||||
default = "deny"
|
||||
copyleft = "deny"
|
||||
allow-osi-fsf-free = "either"
|
||||
confidence-threshold = 0.8
|
||||
|
||||
[[licenses.clarify]]
|
||||
|
@ -70,6 +78,18 @@ version = "0.9"
|
|||
[[bans.skip]]
|
||||
name = "hmac"
|
||||
version = "0.11"
|
||||
[[bans.skip]]
|
||||
name = "zerocopy"
|
||||
version = "0.6"
|
||||
[[bans.skip]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.6"
|
||||
[[bans.skip]]
|
||||
name = "multimap"
|
||||
version = "0.8"
|
||||
[[bans.skip]]
|
||||
name = "nix"
|
||||
version = "0.23"
|
||||
|
||||
# field-offset and nix depend on an older memoffset
|
||||
# https://github.com/Diggsey/rust-field-offset/pull/23
|
||||
|
@ -82,22 +102,23 @@ version = "0.6"
|
|||
[[bans.skip]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1"
|
||||
[[bans.skip]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3"
|
||||
|
||||
# Various crates depend on an older version of base64
|
||||
[[bans.skip]]
|
||||
name = "base64"
|
||||
version = "0.13"
|
||||
[[bans.skip]]
|
||||
name = "base64"
|
||||
version = "0.21"
|
||||
|
||||
# Various crates depend on an older version of socket2
|
||||
[[bans.skip]]
|
||||
name = "socket2"
|
||||
version = "0.4"
|
||||
|
||||
# Various crates depend on an older version of syn
|
||||
[[bans.skip]]
|
||||
name = "syn"
|
||||
version = "1.0"
|
||||
|
||||
# Various crates depend on an older version of bitflags
|
||||
[[bans.skip]]
|
||||
name = "bitflags"
|
||||
|
@ -122,10 +143,18 @@ version = "1.0"
|
|||
name = "hashbrown"
|
||||
version = "0.12"
|
||||
|
||||
# various livekit dependencies depend on an old version of itertools
|
||||
# various livekit dependencies depend on an old version of itertools and sync_wrapper
|
||||
[[bans.skip]]
|
||||
name = "itertools"
|
||||
version = "0.11"
|
||||
[[bans.skip]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1"
|
||||
|
||||
# various rav1e / dssim-core depend on an old version of itertools
|
||||
[[bans.skip]]
|
||||
name = "itertools"
|
||||
version = "0.12"
|
||||
|
||||
# matchers depends on an old version of regex-automata
|
||||
[[bans.skip]]
|
||||
|
@ -179,11 +208,102 @@ version = "0.20"
|
|||
name = "http"
|
||||
version = "0.2"
|
||||
|
||||
# proc-macro-crate depends on an older version of toml_edit
|
||||
# https://github.com/bkchr/proc-macro-crate/pull/50
|
||||
# Various crates depend on an older version of heck
|
||||
[[bans.skip]]
|
||||
name = "toml_edit"
|
||||
name = "heck"
|
||||
version = "0.4"
|
||||
|
||||
# Various crates depend on an older version of hyper / reqwest / headers / etc
|
||||
[[bans.skip]]
|
||||
name = "hyper"
|
||||
version = "0.14"
|
||||
[[bans.skip]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5"
|
||||
[[bans.skip]]
|
||||
name = "http-body"
|
||||
version = "0.4"
|
||||
[[bans.skip]]
|
||||
name = "headers-core"
|
||||
version = "0.2"
|
||||
[[bans.skip]]
|
||||
name = "headers"
|
||||
version = "0.3"
|
||||
[[bans.skip]]
|
||||
name = "h2"
|
||||
version = "0.3"
|
||||
[[bans.skip]]
|
||||
name = "reqwest"
|
||||
version = "0.11"
|
||||
[[bans.skip]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0"
|
||||
[[bans.skip]]
|
||||
name = "winreg"
|
||||
version = "0.50"
|
||||
[[bans.skip]]
|
||||
name = "system-configuration"
|
||||
version = "0.5"
|
||||
[[bans.skip]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5"
|
||||
|
||||
# The AWS SDK uses old versions of rustls and related crates
|
||||
[[bans.skip]]
|
||||
name = "rustls"
|
||||
version = "0.21"
|
||||
[[bans.skip]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6"
|
||||
[[bans.skip]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101"
|
||||
|
||||
# warp depends on an older version of tokio-tungstenite
|
||||
[[bans.skip]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21"
|
||||
[[bans.skip]]
|
||||
name = "tungstenite"
|
||||
version = "0.21"
|
||||
|
||||
# various crates depend on an older version of system-deps
|
||||
[[bans.skip]]
|
||||
name = "system-deps"
|
||||
version = "6"
|
||||
|
||||
# various crates depend on an older version of windows-sys
|
||||
[[bans.skip]]
|
||||
name = "windows-sys"
|
||||
version = "0.52"
|
||||
|
||||
# derived-into-owned (via pcap-file) depends on old syn / quote
|
||||
[[bans.skip]]
|
||||
name = "syn"
|
||||
version = "0.11"
|
||||
[[bans.skip]]
|
||||
name = "quote"
|
||||
version = "0.3"
|
||||
|
||||
# dav1d depends on old system-deps which depends on old cfg-expr
|
||||
[[bans.skip]]
|
||||
name = "cfg-expr"
|
||||
version = "0.15"
|
||||
|
||||
# backtrace and png depend on old miniz_oxide
|
||||
[[bans.skip]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7"
|
||||
|
||||
# tokio-rustls via warp depends on old rustls
|
||||
[[bans.skip]]
|
||||
name = "rustls"
|
||||
version = "0.22"
|
||||
|
||||
# aws-smithy-runtime depends on old tokio-rustls
|
||||
[[bans.skip]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
build_hotdoc = false
|
||||
|
||||
if get_option('doc').disabled()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
if meson.is_cross_build()
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but building the doc while cross building is not supported yet.')
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -81,20 +81,20 @@ impl FileSink {
|
|||
Some(ref location_cur) => {
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Changing `location` from {:?} to {}",
|
||||
location_cur,
|
||||
location,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
gst::info!(CAT, imp: self, "Setting `location` to {}", location,);
|
||||
gst::info!(CAT, imp = self, "Setting `location` to {}", location,);
|
||||
}
|
||||
}
|
||||
Some(location)
|
||||
}
|
||||
None => {
|
||||
gst::info!(CAT, imp: self, "Resetting `location` to None",);
|
||||
gst::info!(CAT, imp = self, "Resetting `location` to None",);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
@ -140,7 +140,12 @@ impl ObjectImpl for FileSink {
|
|||
};
|
||||
|
||||
if let Err(err) = res {
|
||||
gst::error!(CAT, imp: self, "Failed to set property `location`: {}", err);
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp = self,
|
||||
"Failed to set property `location`: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
|
@ -222,10 +227,10 @@ impl BaseSinkImpl for FileSink {
|
|||
]
|
||||
)
|
||||
})?;
|
||||
gst::debug!(CAT, imp: self, "Opened file {:?}", file);
|
||||
gst::debug!(CAT, imp = self, "Opened file {:?}", file);
|
||||
|
||||
*state = State::Started { file, position: 0 };
|
||||
gst::info!(CAT, imp: self, "Started");
|
||||
gst::info!(CAT, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -240,7 +245,7 @@ impl BaseSinkImpl for FileSink {
|
|||
}
|
||||
|
||||
*state = State::Stopped;
|
||||
gst::info!(CAT, imp: self, "Stopped");
|
||||
gst::info!(CAT, imp = self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -260,7 +265,7 @@ impl BaseSinkImpl for FileSink {
|
|||
}
|
||||
};
|
||||
|
||||
gst::trace!(CAT, imp: self, "Rendering {:?}", buffer);
|
||||
gst::trace!(CAT, imp = self, "Rendering {:?}", buffer);
|
||||
let map = buffer.map_readable().map_err(|_| {
|
||||
gst::element_imp_error!(self, gst::CoreError::Failed, ["Failed to map buffer"]);
|
||||
gst::FlowError::Error
|
||||
|
|
|
@ -94,20 +94,20 @@ impl FileSrc {
|
|||
Some(ref location_cur) => {
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Changing `location` from {:?} to {}",
|
||||
location_cur,
|
||||
location,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
gst::info!(CAT, imp: self, "Setting `location to {}", location,);
|
||||
gst::info!(CAT, imp = self, "Setting `location to {}", location,);
|
||||
}
|
||||
}
|
||||
Some(location)
|
||||
}
|
||||
None => {
|
||||
gst::info!(CAT, imp: self, "Resetting `location` to None",);
|
||||
gst::info!(CAT, imp = self, "Resetting `location` to None",);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
@ -148,7 +148,12 @@ impl ObjectImpl for FileSrc {
|
|||
};
|
||||
|
||||
if let Err(err) = res {
|
||||
gst::error!(CAT, imp: self, "Failed to set property `location`: {}", err);
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp = self,
|
||||
"Failed to set property `location`: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
|
@ -250,11 +255,11 @@ impl BaseSrcImpl for FileSrc {
|
|||
)
|
||||
})?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Opened file {:?}", file);
|
||||
gst::debug!(CAT, imp = self, "Opened file {:?}", file);
|
||||
|
||||
*state = State::Started { file, position: 0 };
|
||||
|
||||
gst::info!(CAT, imp: self, "Started");
|
||||
gst::info!(CAT, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -270,7 +275,7 @@ impl BaseSrcImpl for FileSrc {
|
|||
|
||||
*state = State::Stopped;
|
||||
|
||||
gst::info!(CAT, imp: self, "Stopped");
|
||||
gst::info!(CAT, imp = self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
44
generic/gopbuffer/Cargo.toml
Normal file
44
generic/gopbuffer/Cargo.toml
Normal file
|
@ -0,0 +1,44 @@
|
|||
[package]
|
||||
name = "gst-plugin-gopbuffer"
|
||||
version.workspace = true
|
||||
authors = ["Matthew Waters <matthew@centricular.com>"]
|
||||
license = "MPL-2.0"
|
||||
description = "Store complete groups of pictures at a time"
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
gst = { workspace = true, features = ["v1_18"] }
|
||||
gst-video = { workspace = true, features = ["v1_18"] }
|
||||
once_cell.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "gstgopbuffer"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
gst-app = { workspace = true, features = ["v1_18"] }
|
||||
gst-check = { workspace = true, features = ["v1_18"] }
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper.workspace = true
|
||||
|
||||
[features]
|
||||
static = []
|
||||
capi = []
|
||||
|
||||
[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"
|
373
generic/gopbuffer/LICENSE-MPL-2.0
Normal file
373
generic/gopbuffer/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
3
generic/gopbuffer/build.rs
Normal file
3
generic/gopbuffer/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
gst_plugin_version_helper::info()
|
||||
}
|
897
generic/gopbuffer/src/gopbuffer/imp.rs
Normal file
897
generic/gopbuffer/src/gopbuffer/imp.rs
Normal file
|
@ -0,0 +1,897 @@
|
|||
// Copyright (C) 2023 Matthew Waters <matthew@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
|
||||
|
||||
/**
|
||||
* SECTION:element-gopbuffer
|
||||
*
|
||||
* #gopbuffer is an element that can be used to store a minimum duration of data delimited by
|
||||
* discrete GOPs (Group of Picture). It does this in by differentiation on the DELTA_UNIT
|
||||
* flag on each input buffer.
|
||||
*
|
||||
* One example of the usefulness of #gopbuffer is its ability to store a backlog of data starting
|
||||
* on a key frame boundary if say the previous 10s seconds of a stream would like to be recorded to
|
||||
* disk.
|
||||
*
|
||||
* ## Example pipeline
|
||||
*
|
||||
* |[
|
||||
* gst-launch videotestsrc ! vp8enc ! gopbuffer minimum-duration=10000000000 ! fakesink
|
||||
* ]|
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"gopbuffer",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("GopBuffer Element"),
|
||||
)
|
||||
});
|
||||
|
||||
const DEFAULT_MIN_TIME: gst::ClockTime = gst::ClockTime::from_seconds(1);
|
||||
const DEFAULT_MAX_TIME: Option<gst::ClockTime> = None;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
min_time: gst::ClockTime,
|
||||
max_time: Option<gst::ClockTime>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
min_time: DEFAULT_MIN_TIME,
|
||||
max_time: DEFAULT_MAX_TIME,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
|
||||
pub(crate) fn from_caps(caps: &gst::CapsRef) -> Option<Self> {
|
||||
let s = caps.structure(0)?;
|
||||
Some(match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" => DeltaFrames::Bidirectional,
|
||||
"video/x-vp8" | "video/x-vp9" | "video/x-av1" => DeltaFrames::PredictiveOnly,
|
||||
"image/jpeg" | "image/png" | "video/x-raw" => DeltaFrames::IntraOnly,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add buffer list support
|
||||
#[derive(Debug)]
|
||||
enum GopItem {
|
||||
Buffer(gst::Buffer),
|
||||
Event(gst::Event),
|
||||
}
|
||||
|
||||
struct Gop {
|
||||
// all times are in running time
|
||||
start_pts: gst::ClockTime,
|
||||
start_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||
earliest_pts: gst::ClockTime,
|
||||
final_earliest_pts: bool,
|
||||
end_pts: gst::ClockTime,
|
||||
end_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||
final_end_pts: bool,
|
||||
// Buffer or event
|
||||
data: VecDeque<GopItem>,
|
||||
}
|
||||
|
||||
impl Gop {
|
||||
fn push_on_pad(mut self, pad: &gst::Pad) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let mut iter = self.data.iter().filter_map(|item| match item {
|
||||
GopItem::Buffer(buffer) => buffer.pts(),
|
||||
_ => None,
|
||||
});
|
||||
let first_pts = iter.next();
|
||||
let last_pts = iter.last();
|
||||
gst::debug!(
|
||||
CAT,
|
||||
"pushing gop with start pts {} end pts {}",
|
||||
first_pts.display(),
|
||||
last_pts.display(),
|
||||
);
|
||||
for item in self.data.drain(..) {
|
||||
match item {
|
||||
GopItem::Buffer(buffer) => {
|
||||
pad.push(buffer)?;
|
||||
}
|
||||
GopItem::Event(event) => {
|
||||
pad.push_event(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
}
|
||||
|
||||
struct Stream {
|
||||
sinkpad: gst::Pad,
|
||||
srcpad: gst::Pad,
|
||||
|
||||
sink_segment: Option<gst::FormattedSegment<gst::ClockTime>>,
|
||||
|
||||
delta_frames: DeltaFrames,
|
||||
|
||||
queued_gops: VecDeque<Gop>,
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
fn queue_buffer(
|
||||
&mut self,
|
||||
buffer: gst::Buffer,
|
||||
segment: &gst::FormattedSegment<gst::ClockTime>,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let pts_position = buffer.pts().unwrap();
|
||||
let end_pts_position = pts_position
|
||||
.opt_add(buffer.duration())
|
||||
.unwrap_or(pts_position);
|
||||
|
||||
let pts = segment
|
||||
.to_running_time_full(pts_position)
|
||||
.ok_or_else(|| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Couldn't convert PTS to running time"
|
||||
);
|
||||
gst::FlowError::Error
|
||||
})?
|
||||
.positive()
|
||||
.unwrap_or_else(|| {
|
||||
gst::warning!(CAT, obj = self.sinkpad, "Negative PTSs are not supported");
|
||||
gst::ClockTime::ZERO
|
||||
});
|
||||
let end_pts = segment
|
||||
.to_running_time_full(end_pts_position)
|
||||
.ok_or_else(|| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Couldn't convert end PTS to running time"
|
||||
);
|
||||
gst::FlowError::Error
|
||||
})?
|
||||
.positive()
|
||||
.unwrap_or_else(|| {
|
||||
gst::warning!(CAT, obj = self.sinkpad, "Negative PTSs are not supported");
|
||||
gst::ClockTime::ZERO
|
||||
});
|
||||
|
||||
let (dts, end_dts) = if !self.delta_frames.requires_dts() {
|
||||
(None, None)
|
||||
} else {
|
||||
let dts_position = buffer.dts().expect("No dts");
|
||||
let end_dts_position = buffer
|
||||
.duration()
|
||||
.opt_add(dts_position)
|
||||
.unwrap_or(dts_position);
|
||||
|
||||
let dts = segment.to_running_time_full(dts_position).ok_or_else(|| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Couldn't convert DTS to running time"
|
||||
);
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let end_dts = segment
|
||||
.to_running_time_full(end_dts_position)
|
||||
.ok_or_else(|| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Couldn't convert end DTS to running time"
|
||||
);
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let end_dts = std::cmp::max(end_dts, dts);
|
||||
|
||||
(Some(dts), Some(end_dts))
|
||||
};
|
||||
|
||||
if !buffer.flags().contains(gst::BufferFlags::DELTA_UNIT) {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
"New GOP detected with buffer pts {} dts {}",
|
||||
buffer.pts().display(),
|
||||
buffer.dts().display()
|
||||
);
|
||||
let gop = Gop {
|
||||
start_pts: pts,
|
||||
start_dts: dts,
|
||||
earliest_pts: pts,
|
||||
final_earliest_pts: false,
|
||||
end_pts: pts,
|
||||
end_dts,
|
||||
final_end_pts: false,
|
||||
data: VecDeque::from([GopItem::Buffer(buffer)]),
|
||||
};
|
||||
self.queued_gops.push_front(gop);
|
||||
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Updating previous GOP starting at PTS {} to end PTS {}",
|
||||
prev_gop.earliest_pts,
|
||||
pts,
|
||||
);
|
||||
|
||||
prev_gop.end_pts = std::cmp::max(prev_gop.end_pts, pts);
|
||||
prev_gop.end_dts = std::cmp::max(prev_gop.end_dts, dts);
|
||||
|
||||
if !self.delta_frames.requires_dts() {
|
||||
prev_gop.final_end_pts = true;
|
||||
}
|
||||
|
||||
if !prev_gop.final_earliest_pts {
|
||||
// Don't bother logging this for intra-only streams as it would be for every
|
||||
// single buffer.
|
||||
if self.delta_frames.requires_dts() {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Previous GOP has final earliest PTS at {}",
|
||||
prev_gop.earliest_pts
|
||||
);
|
||||
}
|
||||
|
||||
prev_gop.final_earliest_pts = true;
|
||||
if let Some(prev_prev_gop) = self.queued_gops.get_mut(2) {
|
||||
prev_prev_gop.final_end_pts = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(gop) = self.queued_gops.front_mut() {
|
||||
gop.end_pts = std::cmp::max(gop.end_pts, end_pts);
|
||||
gop.end_dts = gop.end_dts.opt_max(end_dts);
|
||||
gop.data.push_back(GopItem::Buffer(buffer));
|
||||
|
||||
if self.delta_frames.requires_dts() {
|
||||
let dts = dts.unwrap();
|
||||
|
||||
if gop.earliest_pts > pts && !gop.final_earliest_pts {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Updating current GOP earliest PTS from {} to {}",
|
||||
gop.earliest_pts,
|
||||
pts
|
||||
);
|
||||
gop.earliest_pts = pts;
|
||||
|
||||
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||
if prev_gop.end_pts < pts {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Updating previous GOP starting PTS {} end time from {} to {}",
|
||||
pts,
|
||||
prev_gop.end_pts,
|
||||
pts
|
||||
);
|
||||
prev_gop.end_pts = pts;
|
||||
}
|
||||
}
|
||||
}
|
||||
let gop = self.queued_gops.front_mut().unwrap();
|
||||
|
||||
// The earliest PTS is known when the current DTS is bigger or equal to the first
|
||||
// PTS that was observed in this GOP. If there was another frame later that had a
|
||||
// lower PTS then it wouldn't be possible to display it in time anymore, i.e. the
|
||||
// stream would be invalid.
|
||||
if gop.start_pts <= dts && !gop.final_earliest_pts {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"GOP has final earliest PTS at {}",
|
||||
gop.earliest_pts
|
||||
);
|
||||
gop.final_earliest_pts = true;
|
||||
|
||||
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||
prev_gop.final_end_pts = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
"dropping buffer before first GOP with pts {} dts {}",
|
||||
buffer.pts().display(),
|
||||
buffer.dts().display()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((prev_gop, first_gop)) = Option::zip(
|
||||
self.queued_gops.iter().find(|gop| gop.final_end_pts),
|
||||
self.queued_gops.back(),
|
||||
) {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Queued full GOPs duration updated to {}",
|
||||
prev_gop.end_pts.saturating_sub(first_gop.earliest_pts),
|
||||
);
|
||||
}
|
||||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Queued duration updated to {}",
|
||||
Option::zip(self.queued_gops.front(), self.queued_gops.back())
|
||||
.map(|(end, start)| end.end_pts.saturating_sub(start.start_pts))
|
||||
.unwrap_or(gst::ClockTime::ZERO)
|
||||
);
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
||||
fn oldest_gop(&mut self) -> Option<Gop> {
|
||||
self.queued_gops.pop_back()
|
||||
}
|
||||
|
||||
fn peek_oldest_gop(&self) -> Option<&Gop> {
|
||||
self.queued_gops.back()
|
||||
}
|
||||
|
||||
fn peek_second_oldest_gop(&self) -> Option<&Gop> {
|
||||
if self.queued_gops.len() <= 1 {
|
||||
return None;
|
||||
}
|
||||
self.queued_gops.get(self.queued_gops.len() - 2)
|
||||
}
|
||||
|
||||
fn drain_all(&mut self) -> impl Iterator<Item = Gop> + '_ {
|
||||
self.queued_gops.drain(..)
|
||||
}
|
||||
|
||||
fn flush(&mut self) {
|
||||
self.queued_gops.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
streams: Vec<Stream>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn stream_from_sink_pad(&self, pad: &gst::Pad) -> Option<&Stream> {
|
||||
self.streams.iter().find(|stream| &stream.sinkpad == pad)
|
||||
}
|
||||
fn stream_from_sink_pad_mut(&mut self, pad: &gst::Pad) -> Option<&mut Stream> {
|
||||
self.streams
|
||||
.iter_mut()
|
||||
.find(|stream| &stream.sinkpad == pad)
|
||||
}
|
||||
fn stream_from_src_pad(&self, pad: &gst::Pad) -> Option<&Stream> {
|
||||
self.streams.iter().find(|stream| &stream.srcpad == pad)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct GopBuffer {
|
||||
state: Mutex<State>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl GopBuffer {
|
||||
fn sink_chain(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let obj = self.obj();
|
||||
if buffer.pts().is_none() {
|
||||
gst::error!(CAT, obj = obj, "Require timestamped buffers!");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let stream = state
|
||||
.stream_from_sink_pad_mut(pad)
|
||||
.expect("pad without an internal Stream");
|
||||
|
||||
let Some(segment) = stream.sink_segment.clone() else {
|
||||
gst::element_imp_error!(self, gst::CoreError::Clock, ["Got buffer before segment"]);
|
||||
return Err(gst::FlowError::Error);
|
||||
};
|
||||
|
||||
if stream.delta_frames.intra_only() && buffer.flags().contains(gst::BufferFlags::DELTA_UNIT)
|
||||
{
|
||||
gst::error!(CAT, obj = pad, "Intra-only stream with delta units");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
if stream.delta_frames.requires_dts() && buffer.dts().is_none() {
|
||||
gst::error!(CAT, obj = pad, "Require DTS for video streams");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
let srcpad = stream.srcpad.clone();
|
||||
stream.queue_buffer(buffer, &segment)?;
|
||||
let mut gops_to_push = vec![];
|
||||
|
||||
let Some(newest_gop) = stream.queued_gops.front() else {
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
};
|
||||
// we are looking for the latest pts value here (which should be the largest)
|
||||
let newest_ts = if stream.delta_frames.requires_dts() {
|
||||
newest_gop.end_dts.unwrap()
|
||||
} else {
|
||||
gst::Signed::Positive(newest_gop.end_pts)
|
||||
};
|
||||
|
||||
loop {
|
||||
// check stored times as though the oldest GOP doesn't exist.
|
||||
let Some(second_oldest_gop) = stream.peek_second_oldest_gop() else {
|
||||
break;
|
||||
};
|
||||
// we are looking for the oldest pts here (with the largest value). This is our potentially
|
||||
// new end time.
|
||||
let oldest_ts = if stream.delta_frames.requires_dts() {
|
||||
second_oldest_gop.start_dts.unwrap()
|
||||
} else {
|
||||
gst::Signed::Positive(second_oldest_gop.start_pts)
|
||||
};
|
||||
|
||||
let stored_duration_without_oldest = newest_ts.saturating_sub(oldest_ts);
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = obj,
|
||||
"newest_pts {}, second oldest_pts {}, stored_duration_without_oldest_gop {}, min-time {}",
|
||||
newest_ts.display(),
|
||||
oldest_ts.display(),
|
||||
stored_duration_without_oldest.display(),
|
||||
settings.min_time.display()
|
||||
);
|
||||
if stored_duration_without_oldest < settings.min_time {
|
||||
break;
|
||||
}
|
||||
gops_to_push.push(stream.oldest_gop().unwrap());
|
||||
}
|
||||
|
||||
if let Some(max_time) = settings.max_time {
|
||||
while let Some(oldest_gop) = stream.peek_oldest_gop() {
|
||||
let oldest_ts = oldest_gop.data.iter().rev().find_map(|item| match item {
|
||||
GopItem::Buffer(buffer) => {
|
||||
if stream.delta_frames.requires_dts() {
|
||||
Some(gst::Signed::Positive(buffer.dts().unwrap()))
|
||||
} else {
|
||||
Some(gst::Signed::Positive(buffer.pts().unwrap()))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
if newest_ts
|
||||
.opt_saturating_sub(oldest_ts)
|
||||
.is_some_and(|diff| diff > gst::Signed::Positive(max_time))
|
||||
{
|
||||
gst::warning!(CAT, obj = obj, "Stored data has overflowed the maximum allowed stored time {}, pushing oldest GOP", max_time.display());
|
||||
gops_to_push.push(stream.oldest_gop().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(state);
|
||||
for gop in gops_to_push.into_iter() {
|
||||
gop.push_on_pad(&srcpad)?;
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
||||
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||
let obj = self.obj();
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let stream = state
|
||||
.stream_from_sink_pad_mut(pad)
|
||||
.expect("pad without an internal Stream!");
|
||||
match event.view() {
|
||||
gst::EventView::Caps(caps) => {
|
||||
let Some(delta_frames) = DeltaFrames::from_caps(caps.caps()) else {
|
||||
return false;
|
||||
};
|
||||
stream.delta_frames = delta_frames;
|
||||
}
|
||||
gst::EventView::FlushStop(_flush) => {
|
||||
gst::debug!(CAT, obj = obj, "flushing stored data");
|
||||
stream.flush();
|
||||
}
|
||||
gst::EventView::Eos(_eos) => {
|
||||
gst::debug!(CAT, obj = obj, "draining data at EOS");
|
||||
let gops = stream.drain_all().collect::<Vec<_>>();
|
||||
let srcpad = stream.srcpad.clone();
|
||||
drop(state);
|
||||
for gop in gops.into_iter() {
|
||||
let _ = gop.push_on_pad(&srcpad);
|
||||
}
|
||||
// once we've pushed all the data, we can push the corresponding eos
|
||||
gst::Pad::event_default(pad, Some(&*obj), event);
|
||||
return true;
|
||||
}
|
||||
gst::EventView::Segment(segment) => {
|
||||
let Ok(segment) = segment.segment().clone().downcast::<gst::ClockTime>() else {
|
||||
gst::error!(CAT, "Non TIME segments are not supported");
|
||||
return false;
|
||||
};
|
||||
stream.sink_segment = Some(segment);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if event.is_serialized() {
|
||||
if stream.peek_oldest_gop().is_none() {
|
||||
// if there is nothing queued, the event can go straight through
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = obj,
|
||||
"nothing queued, event {:?} passthrough",
|
||||
event.structure().map(|s| s.name().as_str())
|
||||
);
|
||||
drop(state);
|
||||
return gst::Pad::event_default(pad, Some(&*obj), event);
|
||||
}
|
||||
let gop = stream.queued_gops.front_mut().unwrap();
|
||||
gop.data.push_back(GopItem::Event(event));
|
||||
true
|
||||
} else {
|
||||
// non-serialized events can be pushed directly
|
||||
drop(state);
|
||||
gst::Pad::event_default(pad, Some(&*obj), event)
|
||||
}
|
||||
}
|
||||
|
||||
fn sink_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||
let obj = self.obj();
|
||||
if query.is_serialized() {
|
||||
// TODO: serialized queries somehow?
|
||||
gst::warning!(
|
||||
CAT,
|
||||
obj = pad,
|
||||
"Serialized queries are currently not supported"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
gst::Pad::query_default(pad, Some(&*obj), query)
|
||||
}
|
||||
|
||||
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||
let obj = self.obj();
|
||||
match query.view_mut() {
|
||||
gst::QueryViewMut::Latency(latency) => {
|
||||
let mut upstream_query = gst::query::Latency::new();
|
||||
let otherpad = {
|
||||
let state = self.state.lock().unwrap();
|
||||
let Some(stream) = state.stream_from_src_pad(pad) else {
|
||||
return false;
|
||||
};
|
||||
stream.sinkpad.clone()
|
||||
};
|
||||
let ret = otherpad.peer_query(&mut upstream_query);
|
||||
|
||||
if ret {
|
||||
let (live, mut min, mut max) = upstream_query.result();
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
min += settings.max_time.unwrap_or(settings.min_time);
|
||||
max = max.opt_max(settings.max_time);
|
||||
|
||||
latency.set(live, min, max);
|
||||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = pad,
|
||||
"Latency query response: live {} min {} max {}",
|
||||
live,
|
||||
min,
|
||||
max.display()
|
||||
);
|
||||
}
|
||||
ret
|
||||
}
|
||||
_ => gst::Pad::query_default(pad, Some(&*obj), query),
|
||||
}
|
||||
}
|
||||
|
||||
fn iterate_internal_links(&self, pad: &gst::Pad) -> gst::Iterator<gst::Pad> {
|
||||
let state = self.state.lock().unwrap();
|
||||
let otherpad = match pad.direction() {
|
||||
gst::PadDirection::Src => state
|
||||
.stream_from_src_pad(pad)
|
||||
.map(|stream| stream.sinkpad.clone()),
|
||||
gst::PadDirection::Sink => state
|
||||
.stream_from_sink_pad(pad)
|
||||
.map(|stream| stream.srcpad.clone()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Some(otherpad) = otherpad {
|
||||
gst::Iterator::from_vec(vec![otherpad])
|
||||
} else {
|
||||
gst::Iterator::from_vec(vec![])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for GopBuffer {
|
||||
const NAME: &'static str = "GstGopBuffer";
|
||||
type Type = super::GopBuffer;
|
||||
type ParentType = gst::Element;
|
||||
}
|
||||
|
||||
impl ObjectImpl for GopBuffer {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecUInt64::builder("minimum-duration")
|
||||
.nick("Minimum Duration")
|
||||
.blurb("The minimum duration to store")
|
||||
.default_value(DEFAULT_MIN_TIME.nseconds())
|
||||
.mutable_ready()
|
||||
.build(),
|
||||
glib::ParamSpecUInt64::builder("max-size-time")
|
||||
.nick("Maximum Duration")
|
||||
.blurb("The maximum duration to store (0=disable)")
|
||||
.default_value(0)
|
||||
.mutable_ready()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
&PROPERTIES
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"minimum-duration" => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
let min_time = value.get().expect("type checked upstream");
|
||||
if settings.min_time != min_time {
|
||||
settings.min_time = min_time;
|
||||
drop(settings);
|
||||
self.post_message(gst::message::Latency::builder().src(&*self.obj()).build());
|
||||
}
|
||||
}
|
||||
"max-size-time" => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
let max_time = value
|
||||
.get::<Option<gst::ClockTime>>()
|
||||
.expect("type checked upstream");
|
||||
let max_time = if matches!(max_time, Some(gst::ClockTime::ZERO) | None) {
|
||||
None
|
||||
} else {
|
||||
max_time
|
||||
};
|
||||
if settings.max_time != max_time {
|
||||
settings.max_time = max_time;
|
||||
drop(settings);
|
||||
self.post_message(gst::message::Latency::builder().src(&*self.obj()).build());
|
||||
}
|
||||
}
|
||||
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"minimum-duration" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.min_time.to_value()
|
||||
}
|
||||
"max-size-time" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.max_time.unwrap_or(gst::ClockTime::ZERO).to_value()
|
||||
}
|
||||
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
let class = obj.class();
|
||||
let templ = class.pad_template("video_sink").unwrap();
|
||||
let sinkpad = gst::Pad::builder_from_template(&templ)
|
||||
.name("video_sink")
|
||||
.chain_function(|pad, parent, buffer| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| Err(gst::FlowError::Error),
|
||||
|gopbuffer| gopbuffer.sink_chain(pad, buffer),
|
||||
)
|
||||
})
|
||||
.event_function(|pad, parent, event| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|gopbuffer| gopbuffer.sink_event(pad, event),
|
||||
)
|
||||
})
|
||||
.query_function(|pad, parent, query| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|gopbuffer| gopbuffer.sink_query(pad, query),
|
||||
)
|
||||
})
|
||||
.iterate_internal_links_function(|pad, parent| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| gst::Pad::iterate_internal_links_default(pad, parent),
|
||||
|gopbuffer| gopbuffer.iterate_internal_links(pad),
|
||||
)
|
||||
})
|
||||
.flags(gst::PadFlags::PROXY_CAPS)
|
||||
.build();
|
||||
obj.add_pad(&sinkpad).unwrap();
|
||||
|
||||
let templ = class.pad_template("video_src").unwrap();
|
||||
let srcpad = gst::Pad::builder_from_template(&templ)
|
||||
.name("video_src")
|
||||
.query_function(|pad, parent, query| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|gopbuffer| gopbuffer.src_query(pad, query),
|
||||
)
|
||||
})
|
||||
.iterate_internal_links_function(|pad, parent| {
|
||||
GopBuffer::catch_panic_pad_function(
|
||||
parent,
|
||||
|| gst::Pad::iterate_internal_links_default(pad, parent),
|
||||
|gopbuffer| gopbuffer.iterate_internal_links(pad),
|
||||
)
|
||||
})
|
||||
.build();
|
||||
obj.add_pad(&srcpad).unwrap();
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.streams.push(Stream {
|
||||
sinkpad,
|
||||
srcpad,
|
||||
sink_segment: None,
|
||||
delta_frames: DeltaFrames::IntraOnly,
|
||||
queued_gops: VecDeque::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for GopBuffer {}
|
||||
|
||||
impl ElementImpl for GopBuffer {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"GopBuffer",
|
||||
"Video",
|
||||
"GOP Buffer",
|
||||
"Matthew Waters <matthew@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
// This element is designed to implement multiple streams but it has not been
|
||||
// implemented.
|
||||
//
|
||||
// The things missing for multiple (audio or video) streams are:
|
||||
// 1. More pad templates
|
||||
// 2. Choosing a main stream to drive the timestamp logic between all input streams
|
||||
// 3. Allowing either the main stream to cause other streams to push data
|
||||
// regardless of it's GOP state, or allow each stream to be individually delimited
|
||||
// by GOP but all still within the minimum duration.
|
||||
let video_caps = [
|
||||
gst::Structure::builder("video/x-h264")
|
||||
.field("stream-format", gst::List::new(["avc", "avc3"]))
|
||||
.field("alignment", "au")
|
||||
.build(),
|
||||
gst::Structure::builder("video/x-h265")
|
||||
.field("stream-format", gst::List::new(["hvc1", "hev1"]))
|
||||
.field("alignment", "au")
|
||||
.build(),
|
||||
gst::Structure::builder("video/x-vp8").build(),
|
||||
gst::Structure::builder("video/x-vp9").build(),
|
||||
gst::Structure::builder("video/x-av1")
|
||||
.field("stream-format", "obu-stream")
|
||||
.field("alignment", "tu")
|
||||
.build(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<gst::Caps>();
|
||||
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"video_src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&video_caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"video_sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&video_caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
#[allow(clippy::single_match)]
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
if let Some(max_time) = settings.max_time {
|
||||
if max_time < settings.min_time {
|
||||
gst::element_imp_error!(
|
||||
self,
|
||||
gst::CoreError::StateChange,
|
||||
["Configured maximum time is less than the minimum time"]
|
||||
);
|
||||
return Err(gst::StateChangeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(transition)?;
|
||||
|
||||
Ok(gst::StateChangeSuccess::Success)
|
||||
}
|
||||
}
|
27
generic/gopbuffer/src/gopbuffer/mod.rs
Normal file
27
generic/gopbuffer/src/gopbuffer/mod.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (C) 2022 Matthew Waters <matthew@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 imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct GopBuffer(ObjectSubclass<imp::GopBuffer>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"gopbuffer",
|
||||
gst::Rank::PRIMARY,
|
||||
GopBuffer::static_type(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
34
generic/gopbuffer/src/lib.rs
Normal file
34
generic/gopbuffer/src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (C) 2022 Matthew Waters <matthew@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-gopbuffer:
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
use gst::glib;
|
||||
|
||||
mod gopbuffer;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gopbuffer::register(plugin)
|
||||
}
|
||||
|
||||
gst::plugin_define!(
|
||||
gopbuffer,
|
||||
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")
|
||||
);
|
128
generic/gopbuffer/tests/tests.rs
Normal file
128
generic/gopbuffer/tests/tests.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
//
|
||||
// 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::*;
|
||||
|
||||
fn init() {
|
||||
use std::sync::Once;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| {
|
||||
gst::init().unwrap();
|
||||
gstgopbuffer::plugin_register_static().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! check_buffer {
|
||||
($buf1:expr, $buf2:expr) => {
|
||||
assert_eq!($buf1.pts(), $buf2.pts());
|
||||
assert_eq!($buf1.dts(), $buf2.dts());
|
||||
assert_eq!($buf1.flags(), $buf2.flags());
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_min_one_gop_held() {
|
||||
const OFFSET: gst::ClockTime = gst::ClockTime::from_seconds(10);
|
||||
init();
|
||||
|
||||
let mut h =
|
||||
gst_check::Harness::with_padnames("gopbuffer", Some("video_sink"), Some("video_src"));
|
||||
|
||||
// 200ms min buffer time
|
||||
let element = h.element().unwrap();
|
||||
element.set_property("minimum-duration", gst::ClockTime::from_mseconds(200));
|
||||
|
||||
h.set_src_caps(
|
||||
gst::Caps::builder("video/x-h264")
|
||||
.field("width", 320i32)
|
||||
.field("height", 240i32)
|
||||
.field("framerate", gst::Fraction::new(10, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::with_size(1).unwrap())
|
||||
.build(),
|
||||
);
|
||||
let mut in_segment = gst::Segment::new();
|
||||
in_segment.set_format(gst::Format::Time);
|
||||
in_segment.set_base(10.seconds());
|
||||
assert!(h.push_event(gst::event::Segment::builder(&in_segment).build()));
|
||||
|
||||
h.play();
|
||||
|
||||
// Push 10 buffers of 100ms each, 2nd and 5th buffer without DELTA_UNIT flag
|
||||
let in_buffers: Vec<_> = (0..6)
|
||||
.map(|i| {
|
||||
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(OFFSET + gst::ClockTime::from_mseconds(i * 100));
|
||||
buffer.set_dts(OFFSET + gst::ClockTime::from_mseconds(i * 100));
|
||||
buffer.set_duration(gst::ClockTime::from_mseconds(100));
|
||||
if i != 1 && i != 4 {
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
}
|
||||
assert_eq!(h.push(buffer.clone()), Ok(gst::FlowSuccess::Ok));
|
||||
buffer
|
||||
})
|
||||
.collect();
|
||||
|
||||
// pull mandatory events
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::StreamStart);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Caps);
|
||||
// GstHarness pushes its own segment event that we need to eat
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Segment);
|
||||
let ev = h.pull_event().unwrap();
|
||||
let gst::event::EventView::Segment(recv_segment) = ev.view() else {
|
||||
unreachable!()
|
||||
};
|
||||
let recv_segment = recv_segment.segment();
|
||||
assert_eq!(recv_segment, &in_segment);
|
||||
|
||||
// check that at least the first GOP has been output already as it exceeds the minimum-time
|
||||
// value
|
||||
let mut in_iter = in_buffers.iter();
|
||||
|
||||
// the first buffer is dropped because it was not preceded by a keyframe
|
||||
let _buffer = in_iter.next().unwrap();
|
||||
|
||||
// a keyframe
|
||||
let out = h.pull().unwrap();
|
||||
let buffer = in_iter.next().unwrap();
|
||||
check_buffer!(buffer, out);
|
||||
|
||||
// not a keyframe
|
||||
let out = h.pull().unwrap();
|
||||
let buffer = in_iter.next().unwrap();
|
||||
check_buffer!(buffer, out);
|
||||
|
||||
// not a keyframe
|
||||
let out = h.pull().unwrap();
|
||||
let buffer = in_iter.next().unwrap();
|
||||
check_buffer!(buffer, out);
|
||||
|
||||
// no more buffers
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
|
||||
// push eos to drain out the rest of the data
|
||||
assert!(h.push_event(gst::event::Eos::new()));
|
||||
for buffer in in_iter {
|
||||
let out = h.pull().unwrap();
|
||||
check_buffer!(buffer, out);
|
||||
}
|
||||
|
||||
// no more buffers
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||
}
|
|
@ -108,7 +108,7 @@ impl ObjectImpl for InterSink {
|
|||
InterStreamProducer::acquire(&settings.producer_name, &appsink)
|
||||
{
|
||||
drop(settings);
|
||||
gst::error!(CAT, imp: self, "{err}");
|
||||
gst::error!(CAT, imp = self, "{err}");
|
||||
self.post_error_message(gst::error_msg!(
|
||||
gst::StreamError::Failed,
|
||||
["{err}"]
|
||||
|
@ -191,7 +191,7 @@ impl ElementImpl for InterSink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
if transition == gst::StateChange::ReadyToPaused {
|
||||
if let Err(err) = self.prepare() {
|
||||
|
|
|
@ -177,7 +177,7 @@ impl ElementImpl for InterSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
if transition == gst::StateChange::ReadyToPaused {
|
||||
if let Err(err) = self.prepare() {
|
||||
|
|
43
generic/originalbuffer/Cargo.toml
Normal file
43
generic/originalbuffer/Cargo.toml
Normal file
|
@ -0,0 +1,43 @@
|
|||
[package]
|
||||
name = "gst-plugin-originalbuffer"
|
||||
version.workspace = true
|
||||
authors = ["Olivier Crête <olivier.crete@collabora.com>"]
|
||||
repository.workspace = true
|
||||
license = "MPL-2.0"
|
||||
description = "GStreamer Origin buffer meta Plugin"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
glib.workspace = true
|
||||
gst.workspace = true
|
||||
gst-video.workspace = true
|
||||
atomic_refcell = "0.1"
|
||||
once_cell.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "gstoriginalbuffer"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper.workspace = true
|
||||
|
||||
[features]
|
||||
static = []
|
||||
capi = []
|
||||
doc = ["gst/v1_16"]
|
||||
|
||||
[package.metadata.capi]
|
||||
min_version = "0.9.21"
|
||||
|
||||
[package.metadata.capi.header]
|
||||
enabled = false
|
||||
|
||||
[package.metadata.capi.library]
|
||||
install_subdir = "gstreamer-1.0"
|
||||
versioning = false
|
||||
import_library = false
|
||||
|
||||
[package.metadata.capi.pkg_config]
|
||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
3
generic/originalbuffer/build.rs
Normal file
3
generic/originalbuffer/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
gst_plugin_version_helper::info()
|
||||
}
|
38
generic/originalbuffer/src/lib.rs
Normal file
38
generic/originalbuffer/src/lib.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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-originalbuffer:
|
||||
*
|
||||
* Since: plugins-rs-0.12 */
|
||||
use gst::glib;
|
||||
|
||||
mod originalbuffermeta;
|
||||
mod originalbufferrestore;
|
||||
mod originalbuffersave;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
originalbuffersave::register(plugin)?;
|
||||
originalbufferrestore::register(plugin)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
gst::plugin_define!(
|
||||
originalbuffer,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||
"MPL",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_REPOSITORY"),
|
||||
env!("BUILD_REL_DATE")
|
||||
);
|
199
generic/originalbuffer/src/originalbuffermeta.rs
Normal file
199
generic/originalbuffer/src/originalbuffermeta.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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 std::fmt;
|
||||
use std::mem;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct OriginalBufferMeta(imp::OriginalBufferMeta);
|
||||
|
||||
unsafe impl Send for OriginalBufferMeta {}
|
||||
unsafe impl Sync for OriginalBufferMeta {}
|
||||
|
||||
impl OriginalBufferMeta {
|
||||
pub fn add(
|
||||
buffer: &mut gst::BufferRef,
|
||||
original: gst::Buffer,
|
||||
caps: Option<gst::Caps>,
|
||||
) -> gst::MetaRefMut<'_, Self, gst::meta::Standalone> {
|
||||
unsafe {
|
||||
// Manually dropping because gst_buffer_add_meta() takes ownership of the
|
||||
// content of the struct
|
||||
let mut params =
|
||||
mem::ManuallyDrop::new(imp::OriginalBufferMetaParams { original, caps });
|
||||
|
||||
let meta = gst::ffi::gst_buffer_add_meta(
|
||||
buffer.as_mut_ptr(),
|
||||
imp::original_buffer_meta_get_info(),
|
||||
&mut *params as *mut imp::OriginalBufferMetaParams as gst::glib::ffi::gpointer,
|
||||
) as *mut imp::OriginalBufferMeta;
|
||||
|
||||
Self::from_mut_ptr(buffer, meta)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, original: gst::Buffer, caps: Option<gst::Caps>) {
|
||||
self.0.original = Some(original);
|
||||
self.0.caps = caps;
|
||||
}
|
||||
|
||||
pub fn original(&self) -> &gst::Buffer {
|
||||
self.0.original.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn caps(&self) -> &gst::Caps {
|
||||
self.0.caps.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl MetaAPI for OriginalBufferMeta {
|
||||
type GstType = imp::OriginalBufferMeta;
|
||||
|
||||
fn meta_api() -> gst::glib::Type {
|
||||
imp::original_buffer_meta_api_get_type()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for OriginalBufferMeta {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("OriginalBufferMeta")
|
||||
.field("buffer", &self.original())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
mod imp {
|
||||
use gst::glib::translate::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
pub(super) struct OriginalBufferMetaParams {
|
||||
pub original: gst::Buffer,
|
||||
pub caps: Option<gst::Caps>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct OriginalBufferMeta {
|
||||
parent: gst::ffi::GstMeta,
|
||||
pub(super) original: Option<gst::Buffer>,
|
||||
pub(super) caps: Option<gst::Caps>,
|
||||
}
|
||||
|
||||
pub(super) fn original_buffer_meta_api_get_type() -> glib::Type {
|
||||
static TYPE: Lazy<glib::Type> = Lazy::new(|| unsafe {
|
||||
let t = from_glib(gst::ffi::gst_meta_api_type_register(
|
||||
b"GstOriginalBufferMetaAPI\0".as_ptr() as *const _,
|
||||
[ptr::null::<std::os::raw::c_char>()].as_ptr() as *mut *const _,
|
||||
));
|
||||
|
||||
assert_ne!(t, glib::Type::INVALID);
|
||||
|
||||
t
|
||||
});
|
||||
|
||||
*TYPE
|
||||
}
|
||||
|
||||
unsafe extern "C" fn original_buffer_meta_init(
|
||||
meta: *mut gst::ffi::GstMeta,
|
||||
params: glib::ffi::gpointer,
|
||||
_buffer: *mut gst::ffi::GstBuffer,
|
||||
) -> glib::ffi::gboolean {
|
||||
assert!(!params.is_null());
|
||||
let meta = &mut *(meta as *mut OriginalBufferMeta);
|
||||
let params = ptr::read(params as *const OriginalBufferMetaParams);
|
||||
|
||||
let OriginalBufferMetaParams { original, caps } = params;
|
||||
|
||||
ptr::write(&mut meta.original, Some(original));
|
||||
ptr::write(&mut meta.caps, caps);
|
||||
|
||||
true.into_glib()
|
||||
}
|
||||
|
||||
unsafe extern "C" fn original_buffer_meta_free(
|
||||
meta: *mut gst::ffi::GstMeta,
|
||||
_buffer: *mut gst::ffi::GstBuffer,
|
||||
) {
|
||||
let meta = &mut *(meta as *mut OriginalBufferMeta);
|
||||
meta.original = None;
|
||||
meta.caps = None;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn original_buffer_meta_transform(
|
||||
dest: *mut gst::ffi::GstBuffer,
|
||||
meta: *mut gst::ffi::GstMeta,
|
||||
_buffer: *mut gst::ffi::GstBuffer,
|
||||
_type_: glib::ffi::GQuark,
|
||||
_data: glib::ffi::gpointer,
|
||||
) -> glib::ffi::gboolean {
|
||||
let dest = gst::BufferRef::from_mut_ptr(dest);
|
||||
let meta = &*(meta as *const OriginalBufferMeta);
|
||||
|
||||
if dest.meta::<super::OriginalBufferMeta>().is_some() {
|
||||
return true.into_glib();
|
||||
}
|
||||
// We don't store a ref in the meta if it's self-refencing, but we add it
|
||||
// when copying the meta to another buffer.
|
||||
super::OriginalBufferMeta::add(
|
||||
dest,
|
||||
meta.original.as_ref().unwrap().clone(),
|
||||
meta.caps.clone(),
|
||||
);
|
||||
|
||||
true.into_glib()
|
||||
}
|
||||
|
||||
pub(super) fn original_buffer_meta_get_info() -> *const gst::ffi::GstMetaInfo {
|
||||
struct MetaInfo(ptr::NonNull<gst::ffi::GstMetaInfo>);
|
||||
unsafe impl Send for MetaInfo {}
|
||||
unsafe impl Sync for MetaInfo {}
|
||||
|
||||
static META_INFO: Lazy<MetaInfo> = Lazy::new(|| unsafe {
|
||||
MetaInfo(
|
||||
ptr::NonNull::new(gst::ffi::gst_meta_register(
|
||||
original_buffer_meta_api_get_type().into_glib(),
|
||||
b"OriginalBufferMeta\0".as_ptr() as *const _,
|
||||
mem::size_of::<OriginalBufferMeta>(),
|
||||
Some(original_buffer_meta_init),
|
||||
Some(original_buffer_meta_free),
|
||||
Some(original_buffer_meta_transform),
|
||||
) as *mut gst::ffi::GstMetaInfo)
|
||||
.expect("Failed to register meta API"),
|
||||
)
|
||||
});
|
||||
|
||||
META_INFO.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
gst::init().unwrap();
|
||||
let mut b = gst::Buffer::with_size(10).unwrap();
|
||||
let caps = gst::Caps::new_empty_simple("video/x-raw");
|
||||
let copy = b.copy();
|
||||
let m = OriginalBufferMeta::add(b.make_mut(), copy, Some(caps.clone()));
|
||||
assert_eq!(m.caps(), caps.as_ref());
|
||||
assert_eq!(m.original().clone(), b);
|
||||
let b2: gst::Buffer = b.copy_deep().unwrap();
|
||||
let m = b.meta::<OriginalBufferMeta>().unwrap();
|
||||
assert_eq!(m.caps(), caps.as_ref());
|
||||
assert_eq!(m.original(), &b);
|
||||
let m = b2.meta::<OriginalBufferMeta>().unwrap();
|
||||
assert_eq!(m.caps(), caps.as_ref());
|
||||
assert_eq!(m.original(), &b);
|
||||
let b3: gst::Buffer = b2.copy_deep().unwrap();
|
||||
drop(b2);
|
||||
let m = b3.meta::<OriginalBufferMeta>().unwrap();
|
||||
assert_eq!(m.caps(), caps.as_ref());
|
||||
assert_eq!(m.original(), &b);
|
||||
}
|
315
generic/originalbuffer/src/originalbufferrestore/imp.rs
Normal file
315
generic/originalbuffer/src/originalbufferrestore/imp.rs
Normal file
|
@ -0,0 +1,315 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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::subclass::prelude::*;
|
||||
use gst_video::prelude::*;
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
|
||||
use crate::originalbuffermeta;
|
||||
use crate::originalbuffermeta::OriginalBufferMeta;
|
||||
|
||||
struct CapsState {
|
||||
caps: gst::Caps,
|
||||
vinfo: Option<gst_video::VideoInfo>,
|
||||
}
|
||||
|
||||
impl Default for CapsState {
|
||||
fn default() -> Self {
|
||||
CapsState {
|
||||
caps: gst::Caps::new_empty(),
|
||||
vinfo: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
sinkpad_caps: CapsState,
|
||||
meta_caps: CapsState,
|
||||
sinkpad_segment: Option<gst::Event>,
|
||||
}
|
||||
|
||||
pub struct OriginalBufferRestore {
|
||||
state: AtomicRefCell<State>,
|
||||
src_pad: gst::Pad,
|
||||
sink_pad: gst::Pad,
|
||||
}
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
#[allow(dead_code)]
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"originalbufferrestore",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Restore Original buffer as meta"),
|
||||
)
|
||||
});
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for OriginalBufferRestore {
|
||||
const NAME: &'static str = "GstOriginalBufferRestore";
|
||||
type Type = super::OriginalBufferRestore;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
let sink_templ = klass.pad_template("sink").unwrap();
|
||||
let src_templ = klass.pad_template("src").unwrap();
|
||||
|
||||
let sink_pad = gst::Pad::builder_from_template(&sink_templ)
|
||||
.chain_function(|pad, parent, buffer| {
|
||||
OriginalBufferRestore::catch_panic_pad_function(
|
||||
parent,
|
||||
|| Err(gst::FlowError::Error),
|
||||
|obj| obj.sink_chain(pad, buffer),
|
||||
)
|
||||
})
|
||||
.event_function(|pad, parent, event| {
|
||||
OriginalBufferRestore::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|obj| obj.sink_event(pad, parent, event),
|
||||
)
|
||||
})
|
||||
.query_function(|pad, parent, query| {
|
||||
OriginalBufferRestore::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|obj| obj.sink_query(pad, parent, query),
|
||||
)
|
||||
})
|
||||
.build();
|
||||
|
||||
let src_pad = gst::Pad::builder_from_template(&src_templ)
|
||||
.event_function(|pad, parent, event| {
|
||||
OriginalBufferRestore::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|obj| obj.src_event(pad, parent, event),
|
||||
)
|
||||
})
|
||||
.build();
|
||||
|
||||
Self {
|
||||
src_pad,
|
||||
sink_pad,
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for OriginalBufferRestore {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(&self.sink_pad).unwrap();
|
||||
obj.add_pad(&self.src_pad).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for OriginalBufferRestore {}
|
||||
|
||||
impl ElementImpl for OriginalBufferRestore {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Original Buffer Restore",
|
||||
"Generic",
|
||||
"Restores a reference to the buffer in a meta",
|
||||
"Olivier Crête <olivier.crete@collabora.com>",
|
||||
)
|
||||
});
|
||||
|
||||
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 src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
let ret = self.parent_change_state(transition)?;
|
||||
if transition == gst::StateChange::PausedToReady {
|
||||
let mut state = self.state.borrow_mut();
|
||||
*state = State::default();
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginalBufferRestore {
|
||||
fn sink_event(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
parent: Option<&impl IsA<gst::Object>>,
|
||||
event: gst::Event,
|
||||
) -> bool {
|
||||
match event.view() {
|
||||
gst::EventView::Caps(e) => {
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
let caps = e.caps_owned();
|
||||
let vinfo = gst_video::VideoInfo::from_caps(&caps).ok();
|
||||
state.sinkpad_caps = CapsState { caps, vinfo };
|
||||
true
|
||||
}
|
||||
gst::EventView::Segment(_) => {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.sinkpad_segment = Some(event);
|
||||
true
|
||||
}
|
||||
_ => gst::Pad::event_default(pad, parent, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn src_event(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
parent: Option<&impl IsA<gst::Object>>,
|
||||
event: gst::Event,
|
||||
) -> bool {
|
||||
if event.type_() == gst::EventType::Reconfigure
|
||||
|| event.has_name("gst-original-buffer-forward-upstream-event")
|
||||
{
|
||||
let s = gst::Structure::builder("gst-original-buffer-forward-upstream-event")
|
||||
.field("event", event)
|
||||
.build();
|
||||
let event = gst::event::CustomUpstream::new(s);
|
||||
self.sink_pad.push_event(event)
|
||||
} else {
|
||||
gst::Pad::event_default(pad, parent, event)
|
||||
}
|
||||
}
|
||||
|
||||
fn sink_query(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
parent: Option<&impl IsA<gst::Object>>,
|
||||
query: &mut gst::QueryRef,
|
||||
) -> bool {
|
||||
if let gst::QueryViewMut::Custom(_) = query.view_mut() {
|
||||
let s = query.structure_mut();
|
||||
if s.has_name("gst-original-buffer-forward-query") {
|
||||
if let Ok(mut q) = s.get::<gst::Query>("query") {
|
||||
s.remove_field("query");
|
||||
assert!(q.is_writable());
|
||||
let res = self.src_pad.peer_query(q.get_mut().unwrap());
|
||||
|
||||
s.set("query", q);
|
||||
s.set("result", res);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::Pad::query_default(pad, parent, query)
|
||||
}
|
||||
|
||||
fn sink_chain(
|
||||
&self,
|
||||
_pad: &gst::Pad,
|
||||
inbuf: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let Some(ometa) = inbuf.meta::<OriginalBufferMeta>() else {
|
||||
//gst::element_warning!(self, gst::StreamError::Failed, ["Buffer {} is missing the GstOriginalBufferMeta, put originalbuffersave upstream in your pipeline", buffer]);
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
};
|
||||
let mut state = self.state.borrow_mut();
|
||||
let meta_caps = &mut state.meta_caps;
|
||||
if &meta_caps.caps != ometa.caps() {
|
||||
if !self.src_pad.push_event(gst::event::Caps::new(ometa.caps())) {
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
meta_caps.caps = ometa.caps().clone();
|
||||
meta_caps.vinfo = gst_video::VideoInfo::from_caps(&meta_caps.caps).ok();
|
||||
}
|
||||
|
||||
let mut outbuf = ometa.original().copy();
|
||||
|
||||
inbuf
|
||||
.copy_into(
|
||||
outbuf.make_mut(),
|
||||
gst::BufferCopyFlags::TIMESTAMPS | gst::BufferCopyFlags::FLAGS,
|
||||
..,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for meta in inbuf.iter_meta::<gst::Meta>() {
|
||||
if meta.api() == originalbuffermeta::OriginalBufferMeta::meta_api() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if meta.has_tag::<gst::meta::tags::Memory>()
|
||||
|| meta.has_tag::<gst::meta::tags::MemoryReference>()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if meta.has_tag::<gst_video::video_meta::tags::Size>() {
|
||||
if let (Some(ref meta_vinfo), Some(ref sink_vinfo)) =
|
||||
(&state.meta_caps.vinfo, &state.sinkpad_caps.vinfo)
|
||||
{
|
||||
if (meta_vinfo.width() != sink_vinfo.width()
|
||||
|| meta_vinfo.height() != sink_vinfo.height())
|
||||
&& meta
|
||||
.transform(
|
||||
outbuf.make_mut(),
|
||||
&gst_video::video_meta::VideoMetaTransformScale::new(
|
||||
sink_vinfo, meta_vinfo,
|
||||
),
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = meta.transform(
|
||||
outbuf.make_mut(),
|
||||
&gst::meta::MetaTransformCopy::new(false, ..),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(event) = state.sinkpad_segment.take() {
|
||||
if !self.src_pad.push_event(event) {
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
}
|
||||
|
||||
self.src_pad.push(outbuf)
|
||||
}
|
||||
}
|
31
generic/originalbuffer/src/originalbufferrestore/mod.rs
Normal file
31
generic/originalbuffer/src/originalbufferrestore/mod.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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
|
||||
|
||||
/**
|
||||
* SECTION:element-originalbufferrestore
|
||||
*
|
||||
* See originalbuffersave for details
|
||||
*/
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct OriginalBufferRestore(ObjectSubclass<imp::OriginalBufferRestore>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"originalbufferrestore",
|
||||
gst::Rank::NONE,
|
||||
OriginalBufferRestore::static_type(),
|
||||
)
|
||||
}
|
205
generic/originalbuffer/src/originalbuffersave/imp.rs
Normal file
205
generic/originalbuffer/src/originalbuffersave/imp.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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::*;
|
||||
use gst::subclass::prelude::*;
|
||||
|
||||
use crate::originalbuffermeta::OriginalBufferMeta;
|
||||
|
||||
pub struct OriginalBufferSave {
|
||||
src_pad: gst::Pad,
|
||||
sink_pad: gst::Pad,
|
||||
}
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
#[allow(dead_code)]
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"originalbuffersave",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Save Original buffer as meta"),
|
||||
)
|
||||
});
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for OriginalBufferSave {
|
||||
const NAME: &'static str = "GstOriginalBufferSave";
|
||||
type Type = super::OriginalBufferSave;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
let sink_templ = klass.pad_template("sink").unwrap();
|
||||
let src_templ = klass.pad_template("src").unwrap();
|
||||
|
||||
let sink_pad = gst::Pad::builder_from_template(&sink_templ)
|
||||
.chain_function(|pad, parent, buffer| {
|
||||
OriginalBufferSave::catch_panic_pad_function(
|
||||
parent,
|
||||
|| Err(gst::FlowError::Error),
|
||||
|obj| obj.sink_chain(pad, buffer),
|
||||
)
|
||||
})
|
||||
.query_function(|pad, parent, query| {
|
||||
OriginalBufferSave::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|obj| obj.sink_query(pad, parent, query),
|
||||
)
|
||||
})
|
||||
.flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::PROXY_ALLOCATION)
|
||||
.build();
|
||||
|
||||
let src_pad = gst::Pad::builder_from_template(&src_templ)
|
||||
.event_function(|pad, parent, event| {
|
||||
OriginalBufferSave::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|obj| obj.src_event(pad, parent, event),
|
||||
)
|
||||
})
|
||||
.build();
|
||||
|
||||
Self { src_pad, sink_pad }
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for OriginalBufferSave {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(&self.sink_pad).unwrap();
|
||||
obj.add_pad(&self.src_pad).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for OriginalBufferSave {}
|
||||
|
||||
impl ElementImpl for OriginalBufferSave {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Original Buffer Save",
|
||||
"Generic",
|
||||
"Saves a reference to the buffer in a meta",
|
||||
"Olivier Crête <olivier.crete@collabora.com>",
|
||||
)
|
||||
});
|
||||
|
||||
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 src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl OriginalBufferSave {
|
||||
fn forward_query(&self, query: gst::Query) -> Option<gst::Query> {
|
||||
let mut s = gst::Structure::new_empty("gst-original-buffer-forward-query");
|
||||
s.set("query", query);
|
||||
|
||||
let mut query = gst::query::Custom::new(s);
|
||||
if self.src_pad.peer_query(&mut query) {
|
||||
let s = query.structure_mut();
|
||||
if let (Ok(true), Ok(q)) = (s.get("result"), s.get::<gst::Query>("query")) {
|
||||
Some(q)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn sink_chain(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
inbuf: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let mut buf = inbuf.copy();
|
||||
let caps = pad.current_caps();
|
||||
|
||||
if let Some(mut meta) = buf.make_mut().meta_mut::<OriginalBufferMeta>() {
|
||||
meta.replace(inbuf, caps);
|
||||
} else {
|
||||
OriginalBufferMeta::add(buf.make_mut(), inbuf, caps);
|
||||
}
|
||||
|
||||
self.src_pad.push(buf)
|
||||
}
|
||||
|
||||
fn sink_query(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
parent: Option<&impl IsA<gst::Object>>,
|
||||
query: &mut gst::QueryRef,
|
||||
) -> bool {
|
||||
let ret = gst::Pad::query_default(pad, parent, query);
|
||||
if !ret {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if let gst::QueryViewMut::Caps(q) = query.view_mut() {
|
||||
if let Some(caps) = q.result_owned() {
|
||||
let forwarding_q = gst::query::Caps::new(Some(&caps)).into();
|
||||
|
||||
if let Some(forwarding_q) = self.forward_query(forwarding_q) {
|
||||
if let gst::QueryView::Caps(c) = forwarding_q.view() {
|
||||
let res = c
|
||||
.result_owned()
|
||||
.map(|c| c.intersect_with_mode(&caps, gst::CapsIntersectMode::First));
|
||||
q.set_result(&res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We should also do allocation queries, but that requires supporting the same
|
||||
// intersection semantics as gsttee, which should be in a helper function.
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn src_event(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
parent: Option<&impl IsA<gst::Object>>,
|
||||
event: gst::Event,
|
||||
) -> bool {
|
||||
let event = if event.has_name("gst-original-buffer-forward-upstream-event") {
|
||||
event.structure().unwrap().get("event").unwrap()
|
||||
} else {
|
||||
event
|
||||
};
|
||||
|
||||
gst::Pad::event_default(pad, parent, event)
|
||||
}
|
||||
}
|
41
generic/originalbuffer/src/originalbuffersave/mod.rs
Normal file
41
generic/originalbuffer/src/originalbuffersave/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright (C) 2024 Collabora Ltd
|
||||
// @author: Olivier Crête <olivier.crete@collabora.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
|
||||
|
||||
/**
|
||||
* SECTION:element-originalbuffersave
|
||||
*
|
||||
* GStreamer elements to store the original buffer and restore it later
|
||||
*
|
||||
* In many analysis scenario (for example machine learning), it is desirable to
|
||||
* use a pre-processed buffer, for example by lowering the resolution, but we may
|
||||
* want to take the output of this analysis, and apply it to the original buffer.
|
||||
*
|
||||
* These elements do just this, the typical usage would be a pipeline like:
|
||||
*
|
||||
* `... ! originalbuffersave ! videoconvertscale ! video/x-raw, width=100, height=100 ! analysiselement ! originalbufferrestore ! ...`
|
||||
*
|
||||
* The originalbufferrestore element will "restore" the buffer that was entered to the "save" element, but will keep any metadata that was added later.
|
||||
*/
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct OriginalBufferSave(ObjectSubclass<imp::OriginalBufferSave>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"originalbuffersave",
|
||||
gst::Rank::NONE,
|
||||
OriginalBufferSave::static_type(),
|
||||
)
|
||||
}
|
|
@ -112,7 +112,7 @@ impl State {
|
|||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
gst::debug!(CAT, obj: pad, "Returned pull size: {}", map.len());
|
||||
gst::debug!(CAT, obj = pad, "Returned pull size: {}", map.len());
|
||||
|
||||
let mut nonce = add_nonce(self.initial_nonce.unwrap(), chunk_index);
|
||||
let block_size = self.block_size.expect("Block size wasn't set") as usize + box_::MACBYTES;
|
||||
|
@ -144,8 +144,8 @@ impl State {
|
|||
adapter_offset: usize,
|
||||
) -> Result<gst::PadGetRangeSuccess, gst::FlowError> {
|
||||
let avail = self.adapter.available();
|
||||
gst::debug!(CAT, obj: pad, "Avail: {}", avail);
|
||||
gst::debug!(CAT, obj: pad, "Adapter offset: {}", adapter_offset);
|
||||
gst::debug!(CAT, obj = pad, "Avail: {}", avail);
|
||||
gst::debug!(CAT, obj = pad, "Adapter offset: {}", adapter_offset);
|
||||
|
||||
// if this underflows, the available buffer in the adapter is smaller than the
|
||||
// requested offset, which means we have reached EOS
|
||||
|
@ -189,7 +189,7 @@ impl State {
|
|||
Err(e) => {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"Failed to map provided buffer writable: {}",
|
||||
e
|
||||
);
|
||||
|
@ -197,7 +197,7 @@ impl State {
|
|||
}
|
||||
};
|
||||
if let Err(e) = self.adapter.copy(0, &mut map[..available_size]) {
|
||||
gst::error!(CAT, obj: pad, "Failed to copy into provided buffer: {}", e);
|
||||
gst::error!(CAT, obj = pad, "Failed to copy into provided buffer: {}", e);
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
if map.len() != available_size {
|
||||
|
@ -278,7 +278,7 @@ impl Decrypter {
|
|||
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling query {:?}", query);
|
||||
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Scheduling(q) => {
|
||||
|
@ -288,12 +288,12 @@ impl Decrypter {
|
|||
return res;
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Upstream returned {:?}", peer_query);
|
||||
gst::log!(CAT, obj = pad, "Upstream returned {:?}", peer_query);
|
||||
|
||||
let (flags, min, max, align) = peer_query.result();
|
||||
q.set(flags, min, max, align);
|
||||
q.add_scheduling_modes(&[gst::PadMode::Pull]);
|
||||
gst::log!(CAT, obj: pad, "Returning {:?}", q.query_mut());
|
||||
gst::log!(CAT, obj = pad, "Returning {:?}", q.query_mut());
|
||||
true
|
||||
}
|
||||
QueryViewMut::Duration(q) => {
|
||||
|
@ -334,7 +334,7 @@ impl Decrypter {
|
|||
// subtrack the MAC of each block
|
||||
let size = size - total_chunks * box_::MACBYTES as u64;
|
||||
|
||||
gst::debug!(CAT, obj: pad, "Setting duration bytes: {}", size);
|
||||
gst::debug!(CAT, obj = pad, "Setting duration bytes: {}", size);
|
||||
q.set(size.bytes());
|
||||
|
||||
true
|
||||
|
@ -402,9 +402,9 @@ impl Decrypter {
|
|||
let state = state.as_mut().unwrap();
|
||||
|
||||
state.initial_nonce = Some(nonce);
|
||||
gst::debug!(CAT, imp: self, "Setting nonce to: {:?}", nonce.0);
|
||||
gst::debug!(CAT, imp = self, "Setting nonce to: {:?}", nonce.0);
|
||||
state.block_size = Some(block_size);
|
||||
gst::debug!(CAT, imp: self, "Setting block size to: {}", block_size);
|
||||
gst::debug!(CAT, imp = self, "Setting block size to: {}", block_size);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -420,8 +420,8 @@ impl Decrypter {
|
|||
+ (chunk_index * block_size as u64)
|
||||
+ (chunk_index * box_::MACBYTES as u64);
|
||||
|
||||
gst::debug!(CAT, obj: pad, "Pull offset: {}", pull_offset);
|
||||
gst::debug!(CAT, obj: pad, "block size: {}", block_size);
|
||||
gst::debug!(CAT, obj = pad, "Pull offset: {}", pull_offset);
|
||||
gst::debug!(CAT, obj = pad, "block size: {}", block_size);
|
||||
|
||||
// calculate how many chunks are needed, if we need something like 3.2
|
||||
// round the number to 4 and cut the buffer afterwards.
|
||||
|
@ -440,7 +440,7 @@ impl Decrypter {
|
|||
|
||||
// Read at least one chunk in case 0 bytes were requested
|
||||
let total_chunks = u32::max((checked - 1) / block_size, 1);
|
||||
gst::debug!(CAT, obj: pad, "Blocks to be pulled: {}", total_chunks);
|
||||
gst::debug!(CAT, obj = pad, "Blocks to be pulled: {}", total_chunks);
|
||||
|
||||
// Pull a buffer of all the chunks we will need
|
||||
let checked_size = total_chunks.checked_mul(block_size).ok_or_else(|| {
|
||||
|
@ -457,18 +457,29 @@ impl Decrypter {
|
|||
})?;
|
||||
|
||||
let total_size = checked_size + (total_chunks * box_::MACBYTES as u32);
|
||||
gst::debug!(CAT, obj: pad, "Requested pull size: {}", total_size);
|
||||
gst::debug!(CAT, obj = pad, "Requested pull size: {}", total_size);
|
||||
|
||||
self.sinkpad.pull_range(pull_offset, total_size).map_err(|err| {
|
||||
self.sinkpad
|
||||
.pull_range(pull_offset, total_size)
|
||||
.map_err(|err| {
|
||||
match err {
|
||||
gst::FlowError::Flushing => {
|
||||
gst::debug!(CAT, obj: self.sinkpad, "Pausing after pulling buffer, reason: flushing");
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Pausing after pulling buffer, reason: flushing"
|
||||
);
|
||||
}
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj: self.sinkpad, "Eos");
|
||||
gst::debug!(CAT, obj = self.sinkpad, "Eos");
|
||||
}
|
||||
flow => {
|
||||
gst::error!(CAT, obj: self.sinkpad, "Failed to pull, reason: {:?}", flow);
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = self.sinkpad,
|
||||
"Failed to pull, reason: {:?}",
|
||||
flow
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -493,14 +504,14 @@ impl Decrypter {
|
|||
state.block_size.expect("Block size wasn't set")
|
||||
};
|
||||
|
||||
gst::debug!(CAT, obj: pad, "Requested offset: {}", offset);
|
||||
gst::debug!(CAT, obj: pad, "Requested size: {}", requested_size);
|
||||
gst::debug!(CAT, obj = pad, "Requested offset: {}", offset);
|
||||
gst::debug!(CAT, obj = pad, "Requested size: {}", requested_size);
|
||||
|
||||
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);
|
||||
assert!(pull_offset <= std::u32::MAX as u64);
|
||||
assert!(pull_offset <= u32::MAX as u64);
|
||||
let pull_offset = pull_offset as u32;
|
||||
|
||||
let pulled_buffer =
|
||||
|
@ -670,7 +681,7 @@ impl ElementImpl for Decrypter {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::debug!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::debug!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -166,7 +166,7 @@ impl Encrypter {
|
|||
pad: &gst::Pad,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::log!(CAT, obj: pad, "Handling buffer {:?}", buffer);
|
||||
gst::log!(CAT, obj = pad, "Handling buffer {:?}", buffer);
|
||||
|
||||
let mut buffers = BufferVec::new();
|
||||
let mut state_guard = self.state.lock().unwrap();
|
||||
|
@ -193,7 +193,7 @@ impl Encrypter {
|
|||
|
||||
for buffer in buffers {
|
||||
self.srcpad.push(buffer).map_err(|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to push buffer {:?}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to push buffer {:?}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ impl Encrypter {
|
|||
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling event {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::Caps(_) => {
|
||||
|
@ -236,7 +236,7 @@ impl Encrypter {
|
|||
|
||||
for buffer in buffers {
|
||||
if let Err(err) = self.srcpad.push(buffer) {
|
||||
gst::error!(CAT, imp: self, "Failed to push buffer at EOS {:?}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to push buffer at EOS {:?}", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ impl Encrypter {
|
|||
fn src_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling event {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::Seek(_) => false,
|
||||
|
@ -261,7 +261,7 @@ impl Encrypter {
|
|||
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling query {:?}", query);
|
||||
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Seeking(q) => {
|
||||
|
@ -271,7 +271,7 @@ impl Encrypter {
|
|||
gst::GenericFormattedValue::none_for_format(format),
|
||||
gst::GenericFormattedValue::none_for_format(format),
|
||||
);
|
||||
gst::log!(CAT, obj: pad, "Returning {:?}", q.query_mut());
|
||||
gst::log!(CAT, obj = pad, "Returning {:?}", q.query_mut());
|
||||
true
|
||||
}
|
||||
QueryViewMut::Duration(q) => {
|
||||
|
@ -311,7 +311,7 @@ impl Encrypter {
|
|||
// add static offsets
|
||||
let size = size + crate::HEADERS_SIZE as u64;
|
||||
|
||||
gst::debug!(CAT, obj: pad, "Setting duration bytes: {}", size);
|
||||
gst::debug!(CAT, obj = pad, "Setting duration bytes: {}", size);
|
||||
q.set(size.bytes());
|
||||
|
||||
true
|
||||
|
@ -492,7 +492,7 @@ impl ElementImpl for Encrypter {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::debug!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::debug!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -84,13 +84,16 @@ fn main() {
|
|||
.property("signal-handoffs", true)
|
||||
.build()
|
||||
.unwrap();
|
||||
sink.connect(
|
||||
sink.connect_closure(
|
||||
"handoff",
|
||||
true,
|
||||
glib::clone!(@strong counter => move |_| {
|
||||
glib::closure!(
|
||||
#[strong]
|
||||
counter,
|
||||
move |_fakesink: &gst::Element, _buffer: &gst::Buffer, _pad: &gst::Pad| {
|
||||
let _ = counter.fetch_add(1, Ordering::SeqCst);
|
||||
None
|
||||
}),
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
let (source, context) = match source.as_str() {
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
macro_rules! debug_or_trace {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident: $obj:expr, $rest:tt $(,)?) => {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident = $obj:expr, $rest:tt $(,)?) => {
|
||||
if $raise_log_level {
|
||||
gst::debug!($cat, $qual: $obj, $rest);
|
||||
gst::debug!($cat, $qual = $obj, $rest);
|
||||
} else {
|
||||
gst::trace!($cat, $qual: $obj, $rest);
|
||||
gst::trace!($cat, $qual = $obj, $rest);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! log_or_trace {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident: $obj:expr, $rest:tt $(,)?) => {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident = $obj:expr, $rest:tt $(,)?) => {
|
||||
if $raise_log_level {
|
||||
gst::log!($cat, $qual: $obj, $rest);
|
||||
gst::log!($cat, $qual = $obj, $rest);
|
||||
} else {
|
||||
gst::trace!($cat, $qual: $obj, $rest);
|
||||
gst::trace!($cat, $qual = $obj, $rest);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,14 +43,14 @@ impl PadSinkHandlerInner {
|
|||
log_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
obj = elem,
|
||||
"Discarding {buffer:?} (flushing)"
|
||||
);
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "Received {buffer:?}");
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj = elem, "Received {buffer:?}");
|
||||
|
||||
let dts = buffer
|
||||
.dts()
|
||||
|
@ -67,18 +67,23 @@ impl PadSinkHandlerInner {
|
|||
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,
|
||||
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");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = elem, "Buffer processed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -117,7 +122,7 @@ impl PadSinkHandler for AsyncPadSinkHandler {
|
|||
EventView::Eos(_) => {
|
||||
{
|
||||
let mut inner = self.0.lock().await;
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj: elem, "EOS");
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj = elem, "EOS");
|
||||
inner.is_flushing = true;
|
||||
}
|
||||
|
||||
|
@ -196,7 +201,7 @@ pub struct AsyncMutexSink {
|
|||
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");
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp = self, "Preparing");
|
||||
let stats = if settings.logs_stats {
|
||||
Some(Stats::new(
|
||||
settings.max_buffers,
|
||||
|
@ -207,25 +212,25 @@ impl AsyncMutexSink {
|
|||
};
|
||||
|
||||
self.sink_pad_handler.prepare(settings.is_main_elem, stats);
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -311,7 +316,7 @@ impl ElementImpl for AsyncMutexSink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
gst::trace!(CAT, imp = self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -42,14 +42,14 @@ impl PadSinkHandlerInner {
|
|||
log_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
obj = elem,
|
||||
"Discarding {buffer:?} (flushing)"
|
||||
);
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "Received {buffer:?}");
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj = elem, "Received {buffer:?}");
|
||||
|
||||
let dts = buffer
|
||||
.dts()
|
||||
|
@ -66,18 +66,23 @@ impl PadSinkHandlerInner {
|
|||
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,
|
||||
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");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = elem, "Buffer processed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -116,7 +121,7 @@ impl PadSinkHandler for SyncPadSinkHandler {
|
|||
EventView::Eos(_) => {
|
||||
{
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj: elem, "EOS");
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj = elem, "EOS");
|
||||
inner.is_flushing = true;
|
||||
}
|
||||
|
||||
|
@ -189,7 +194,7 @@ pub struct DirectSink {
|
|||
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");
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp = self, "Preparing");
|
||||
let stats = if settings.logs_stats {
|
||||
Some(Stats::new(
|
||||
settings.max_buffers,
|
||||
|
@ -200,25 +205,25 @@ impl DirectSink {
|
|||
};
|
||||
|
||||
self.sink_pad_handler.prepare(settings.is_main_elem, stats);
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -304,7 +309,7 @@ impl ElementImpl for DirectSink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
gst::trace!(CAT, imp = self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -68,7 +68,7 @@ impl PadSinkHandler for TaskPadSinkHandler {
|
|||
}
|
||||
EventView::Eos(_) => {
|
||||
let is_main_elem = elem.imp().settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, obj: elem, "EOS");
|
||||
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.
|
||||
|
@ -137,13 +137,13 @@ 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");
|
||||
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");
|
||||
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();
|
||||
|
@ -156,7 +156,7 @@ impl TaskImpl for TaskSinkTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Stopping Task");
|
||||
self.flush();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ impl TaskImpl for TaskSinkTask {
|
|||
|
||||
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:?}");
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Received {item:?}");
|
||||
|
||||
match item {
|
||||
StreamItem::Buffer(buffer) => {
|
||||
|
@ -194,20 +194,20 @@ impl TaskImpl for TaskSinkTask {
|
|||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: self.elem,
|
||||
obj = self.elem,
|
||||
"o latency {latency:.2?}",
|
||||
);
|
||||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: self.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");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Buffer processed");
|
||||
}
|
||||
StreamItem::Event(evt) => {
|
||||
if let EventView::Segment(evt) = evt.view() {
|
||||
|
@ -249,7 +249,7 @@ impl TaskSink {
|
|||
None
|
||||
};
|
||||
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Preparing");
|
||||
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!(
|
||||
|
@ -265,32 +265,32 @@ impl TaskSink {
|
|||
|
||||
*self.item_sender.lock().unwrap() = Some(item_sender);
|
||||
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -376,7 +376,7 @@ impl ElementImpl for TaskSink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
gst::trace!(CAT, imp = self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -103,7 +103,7 @@ impl TaskImpl for SrcTask {
|
|||
let settings = imp.settings.lock().unwrap();
|
||||
self.is_main_elem = settings.is_main_elem;
|
||||
|
||||
log_or_trace!(CAT, self.is_main_elem, imp: imp, "Preparing Task");
|
||||
log_or_trace!(CAT, self.is_main_elem, imp = imp, "Preparing Task");
|
||||
|
||||
self.push_period = settings.push_period;
|
||||
self.num_buffers = settings.num_buffers;
|
||||
|
@ -113,12 +113,17 @@ impl TaskImpl for SrcTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Starting Task");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Starting Task");
|
||||
|
||||
if self.need_initial_events {
|
||||
let imp = self.elem.imp();
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing initial events");
|
||||
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>());
|
||||
|
@ -157,7 +162,7 @@ impl TaskImpl for SrcTask {
|
|||
}
|
||||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Stopping Task");
|
||||
self.buffer_pool.set_active(false).unwrap();
|
||||
self.timer = None;
|
||||
self.need_initial_events = true;
|
||||
|
@ -167,9 +172,9 @@ impl TaskImpl for SrcTask {
|
|||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Awaiting timer");
|
||||
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");
|
||||
log_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Timer ticked");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -190,13 +195,18 @@ impl TaskImpl for SrcTask {
|
|||
buffer
|
||||
})
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, obj: self.elem, "Failed to acquire buffer {err}");
|
||||
gst::error!(CAT, obj = self.elem, "Failed to acquire buffer {err}");
|
||||
err
|
||||
})?;
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Forwarding buffer");
|
||||
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");
|
||||
log_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj = self.elem,
|
||||
"Successfully pushed buffer"
|
||||
);
|
||||
|
||||
self.buffer_count += 1;
|
||||
|
||||
|
@ -213,22 +223,22 @@ impl TaskImpl for SrcTask {
|
|||
async move {
|
||||
match err {
|
||||
gst::FlowError::Eos => {
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing EOS");
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Pushing 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");
|
||||
gst::error!(CAT, imp = imp, "Error pushing EOS");
|
||||
}
|
||||
|
||||
task::Trigger::Stop
|
||||
}
|
||||
gst::FlowError::Flushing => {
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Flushing");
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj = self.elem, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
err => {
|
||||
gst::error!(CAT, obj: self.elem, "Got error {err}");
|
||||
gst::error!(CAT, obj = self.elem, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.elem,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -254,7 +264,7 @@ pub struct TestSrc {
|
|||
impl TestSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Preparing");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
|
@ -269,41 +279,41 @@ impl TestSrc {
|
|||
.prepare(SrcTask::new(self.obj().clone()), ts_ctx)
|
||||
.block_on()?;
|
||||
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Prepared");
|
||||
debug_or_trace!(CAT, 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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Pausing");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Paused");
|
||||
debug_or_trace!(CAT, is_main_elem, imp = self, "Paused");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -453,7 +463,7 @@ impl ElementImpl for TestSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
gst::trace!(CAT, imp = self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -30,7 +30,6 @@ use once_cell::sync::Lazy;
|
|||
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::u32;
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{Context, PadSrc, Task, TaskState};
|
||||
|
@ -83,7 +82,7 @@ impl PadSrcHandler for AppSrcPadHandler {
|
|||
type ElementImpl = AppSrc;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &AppSrc, event: gst::Event) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
use gst::EventView;
|
||||
let ret = match event.view() {
|
||||
|
@ -95,16 +94,16 @@ impl PadSrcHandler for AppSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", event);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", event);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &AppSrc, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
use gst::QueryViewMut;
|
||||
let ret = match query.view_mut() {
|
||||
|
@ -136,9 +135,9 @@ impl PadSrcHandler for AppSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", query);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", query);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
@ -170,11 +169,11 @@ impl AppSrcTask {
|
|||
}
|
||||
|
||||
async fn push_item(&mut self, item: StreamItem) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::log!(CAT, obj: self.element, "Handling {:?}", item);
|
||||
gst::log!(CAT, obj = self.element, "Handling {:?}", item);
|
||||
let appsrc = self.element.imp();
|
||||
|
||||
if self.need_initial_events {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing initial events");
|
||||
gst::debug!(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)
|
||||
|
@ -204,7 +203,7 @@ impl AppSrcTask {
|
|||
|
||||
match item {
|
||||
StreamItem::Buffer(buffer) => {
|
||||
gst::log!(CAT, obj: self.element, "Forwarding {:?}", buffer);
|
||||
gst::log!(CAT, obj = self.element, "Forwarding {:?}", buffer);
|
||||
appsrc.src_pad.push(buffer).await
|
||||
}
|
||||
StreamItem::Event(event) => {
|
||||
|
@ -214,7 +213,7 @@ impl AppSrcTask {
|
|||
Err(gst::FlowError::Eos)
|
||||
}
|
||||
_ => {
|
||||
gst::log!(CAT, obj: self.element, "Forwarding {:?}", event);
|
||||
gst::log!(CAT, obj = self.element, "Forwarding {:?}", event);
|
||||
appsrc.src_pad.push_event(event).await;
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
@ -242,18 +241,18 @@ impl TaskImpl for AppSrcTask {
|
|||
let res = self.push_item(item).await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
gst::log!(CAT, obj: self.element, "Successfully pushed item");
|
||||
gst::log!(CAT, obj = self.element, "Successfully pushed item");
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
let appsrc = self.element.imp();
|
||||
appsrc.src_pad.push_event(gst::event::Eos::new()).await;
|
||||
}
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
gst::debug!(CAT, obj: self.element, "Flushing");
|
||||
gst::debug!(CAT, obj = self.element, "Flushing");
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
||||
gst::error!(CAT, obj = self.element, "Got error {}", err);
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -270,13 +269,13 @@ impl TaskImpl for AppSrcTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task");
|
||||
|
||||
self.flush();
|
||||
self.need_initial_events = true;
|
||||
self.need_segment = true;
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -284,12 +283,12 @@ impl TaskImpl for AppSrcTask {
|
|||
|
||||
fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Starting task flush");
|
||||
gst::log!(CAT, obj = self.element, "Starting task flush");
|
||||
|
||||
self.flush();
|
||||
self.need_segment = true;
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task flush started");
|
||||
gst::log!(CAT, obj = self.element, "Task flush started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -309,7 +308,7 @@ impl AppSrc {
|
|||
fn push_buffer(&self, mut buffer: gst::Buffer) -> bool {
|
||||
let state = self.task.lock_state();
|
||||
if *state != TaskState::Started && *state != TaskState::Paused {
|
||||
gst::debug!(CAT, imp: self, "Rejecting buffer due to element state");
|
||||
gst::debug!(CAT, imp = self, "Rejecting buffer due to element state");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -324,7 +323,7 @@ impl AppSrc {
|
|||
buffer.set_dts(now.opt_checked_sub(base_time).ok().flatten());
|
||||
buffer.set_pts(None);
|
||||
} else {
|
||||
gst::error!(CAT, imp: self, "Don't have a clock yet");
|
||||
gst::error!(CAT, imp = self, "Don't have a clock yet");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -337,7 +336,7 @@ impl AppSrc {
|
|||
{
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to queue buffer: {}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to queue buffer: {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -353,14 +352,14 @@ impl AppSrc {
|
|||
match sender.try_send(StreamItem::Event(gst::event::Eos::new())) {
|
||||
Ok(_) => true,
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to queue EOS: {}", err);
|
||||
gst::error!(CAT, imp = self, "Failed to queue EOS: {}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let context =
|
||||
|
@ -387,38 +386,38 @@ impl AppSrc {
|
|||
.prepare(AppSrcTask::new(self.obj().clone(), receiver), context)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
gst::debug!(CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
|
||||
*self.sender.lock().unwrap() = None;
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
gst::debug!(CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
gst::debug!(CAT, imp = self, "Paused");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -599,7 +598,7 @@ impl ElementImpl for AppSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -90,7 +90,7 @@ 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:?}");
|
||||
gst::debug!(CAT, obj = pad, "Received {query:?}");
|
||||
|
||||
if let gst::QueryViewMut::Latency(q) = query.view_mut() {
|
||||
let settings = imp.settings.lock().unwrap();
|
||||
|
@ -187,17 +187,17 @@ impl AudioTestSrcTask {
|
|||
}
|
||||
|
||||
let mut caps = pad.peer_query_caps(Some(&DEFAULT_CAPS));
|
||||
gst::debug!(CAT, imp: imp, "Peer returned {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}");
|
||||
gst::error!(CAT, imp = imp, "{err}");
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if caps.is_any() {
|
||||
gst::debug!(CAT, imp: imp, "Using our own Caps");
|
||||
gst::debug!(CAT, imp = imp, "Using our own Caps");
|
||||
caps = DEFAULT_CAPS.clone();
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ impl AudioTestSrcTask {
|
|||
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}");
|
||||
gst::error!(CAT, imp = imp, "{err}");
|
||||
err
|
||||
})?;
|
||||
|
||||
|
@ -227,7 +227,7 @@ impl AudioTestSrcTask {
|
|||
}
|
||||
|
||||
caps.fixate();
|
||||
gst::debug!(CAT, imp: imp, "fixated to {caps:?}");
|
||||
gst::debug!(CAT, imp = imp, "fixated to {caps:?}");
|
||||
|
||||
imp.src_pad.push_event(gst::event::Caps::new(&caps)).await;
|
||||
|
||||
|
@ -241,7 +241,7 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
type Item = gst::Buffer;
|
||||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
gst::log!(CAT, obj: self.elem, "Preparing Task");
|
||||
gst::log!(CAT, obj = self.elem, "Preparing Task");
|
||||
|
||||
let imp = self.elem.imp();
|
||||
let settings = imp.settings.lock().unwrap();
|
||||
|
@ -260,10 +260,10 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.elem, "Starting Task");
|
||||
gst::log!(CAT, obj = self.elem, "Starting Task");
|
||||
|
||||
if self.need_initial_events {
|
||||
gst::debug!(CAT, obj: self.elem, "Pushing initial events");
|
||||
gst::debug!(CAT, obj = self.elem, "Pushing initial events");
|
||||
|
||||
let stream_id =
|
||||
format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
|
||||
|
@ -311,14 +311,14 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
}
|
||||
|
||||
fn pause(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
gst::log!(CAT, obj: self.elem, "Pausing Task");
|
||||
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");
|
||||
gst::log!(CAT, obj = self.elem, "Stopping Task");
|
||||
|
||||
self.need_initial_events = true;
|
||||
self.accumulator = 0.0;
|
||||
|
@ -331,7 +331,7 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
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);
|
||||
gst::error!(CAT, obj = self.elem, "Failed to acquire buffer {}", err);
|
||||
return future::err(err).boxed();
|
||||
}
|
||||
};
|
||||
|
@ -399,9 +399,9 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
async move {
|
||||
let imp = self.elem.imp();
|
||||
|
||||
gst::debug!(CAT, imp: imp, "Pushing {buffer:?}");
|
||||
gst::debug!(CAT, imp = imp, "Pushing {buffer:?}");
|
||||
imp.src_pad.push(buffer).await?;
|
||||
gst::log!(CAT, imp: imp, "Successfully pushed buffer");
|
||||
gst::log!(CAT, imp = imp, "Successfully pushed buffer");
|
||||
|
||||
self.buffer_count += 1;
|
||||
|
||||
|
@ -442,12 +442,12 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
async move {
|
||||
match err {
|
||||
gst::FlowError::Flushing => {
|
||||
gst::debug!(CAT, obj: self.elem, "Flushing");
|
||||
gst::debug!(CAT, obj = self.elem, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj: self.elem, "EOS");
|
||||
gst::debug!(CAT, obj = self.elem, "EOS");
|
||||
self.elem
|
||||
.imp()
|
||||
.src_pad
|
||||
|
@ -457,7 +457,7 @@ impl TaskImpl for AudioTestSrcTask {
|
|||
task::Trigger::Stop
|
||||
}
|
||||
err => {
|
||||
gst::error!(CAT, obj: self.elem, "Got error {err}");
|
||||
gst::error!(CAT, obj = self.elem, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.elem,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -482,7 +482,7 @@ pub struct AudioTestSrc {
|
|||
|
||||
impl AudioTestSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let context =
|
||||
|
@ -498,37 +498,37 @@ impl AudioTestSrc {
|
|||
.prepare(AudioTestSrcTask::new(self.obj().clone()), context)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
gst::debug!(CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
gst::debug!(CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
gst::debug!(CAT, imp = self, "Paused");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -695,7 +695,7 @@ impl ElementImpl for AudioTestSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
gst::trace!(CAT, imp = self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -26,7 +26,6 @@ use once_cell::sync::Lazy;
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::u32;
|
||||
|
||||
static DATA_QUEUE_CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
|
@ -127,10 +126,14 @@ impl DataQueue {
|
|||
pub fn start(&self) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
if inner.state == DataQueueState::Started {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Data queue already Started");
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Data queue already Started"
|
||||
);
|
||||
return;
|
||||
}
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Starting data queue");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Starting data queue");
|
||||
inner.state = DataQueueState::Started;
|
||||
inner.wake();
|
||||
}
|
||||
|
@ -138,10 +141,14 @@ impl DataQueue {
|
|||
pub fn stop(&self) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
if inner.state == DataQueueState::Stopped {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Data queue already Stopped");
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Data queue already Stopped"
|
||||
);
|
||||
return;
|
||||
}
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Stopping data queue");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Stopping data queue");
|
||||
inner.state = DataQueueState::Stopped;
|
||||
inner.wake();
|
||||
}
|
||||
|
@ -149,7 +156,7 @@ impl DataQueue {
|
|||
pub fn clear(&self) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Clearing data queue");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Clearing data queue");
|
||||
|
||||
let src_pad = inner.src_pad.clone();
|
||||
for item in inner.queue.drain(..) {
|
||||
|
@ -163,7 +170,7 @@ impl DataQueue {
|
|||
}
|
||||
}
|
||||
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Data queue cleared");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Data queue cleared");
|
||||
}
|
||||
|
||||
pub fn push(&self, item: DataQueueItem) -> Result<(), DataQueueItem> {
|
||||
|
@ -172,7 +179,7 @@ impl DataQueue {
|
|||
if inner.state == DataQueueState::Stopped {
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj: inner.element,
|
||||
obj = inner.element,
|
||||
"Rejecting item {:?} in state {:?}",
|
||||
item,
|
||||
inner.state
|
||||
|
@ -180,7 +187,12 @@ impl DataQueue {
|
|||
return Err(item);
|
||||
}
|
||||
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Pushing item {:?}", item);
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Pushing item {:?}",
|
||||
item
|
||||
);
|
||||
|
||||
let (count, bytes) = item.size();
|
||||
let queue_ts = inner.queue.iter().filter_map(|i| i.timestamp()).next();
|
||||
|
@ -188,14 +200,26 @@ impl DataQueue {
|
|||
|
||||
if let Some(max) = inner.max_size_buffers {
|
||||
if max <= inner.cur_size_buffers {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Queue is full (buffers): {} <= {}", max, inner.cur_size_buffers);
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Queue is full (buffers): {} <= {}",
|
||||
max,
|
||||
inner.cur_size_buffers
|
||||
);
|
||||
return Err(item);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max) = inner.max_size_bytes {
|
||||
if max <= inner.cur_size_bytes {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Queue is full (bytes): {} <= {}", max, inner.cur_size_bytes);
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Queue is full (bytes): {} <= {}",
|
||||
max,
|
||||
inner.cur_size_bytes
|
||||
);
|
||||
return Err(item);
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +233,13 @@ impl DataQueue {
|
|||
};
|
||||
|
||||
if max <= level {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Queue is full (time): {} <= {}", max, level);
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Queue is full (time): {} <= {}",
|
||||
max,
|
||||
level
|
||||
);
|
||||
return Err(item);
|
||||
}
|
||||
}
|
||||
|
@ -232,10 +262,15 @@ impl DataQueue {
|
|||
match inner.state {
|
||||
DataQueueState::Started => match inner.queue.pop_front() {
|
||||
None => {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Data queue is empty");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Data queue is empty");
|
||||
}
|
||||
Some(item) => {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Popped item {:?}", item);
|
||||
gst::debug!(
|
||||
DATA_QUEUE_CAT,
|
||||
obj = inner.element,
|
||||
"Popped item {:?}",
|
||||
item
|
||||
);
|
||||
|
||||
let (count, bytes) = item.size();
|
||||
inner.cur_size_buffers -= count;
|
||||
|
@ -245,7 +280,7 @@ impl DataQueue {
|
|||
}
|
||||
},
|
||||
DataQueueState::Stopped => {
|
||||
gst::debug!(DATA_QUEUE_CAT, obj: inner.element, "Data queue Stopped");
|
||||
gst::debug!(DATA_QUEUE_CAT, obj = inner.element, "Data queue Stopped");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ use once_cell::sync::Lazy;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::u32;
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{self, PadSink, PadSrc};
|
||||
|
@ -140,7 +139,7 @@ impl InputSelectorPadSinkHandler {
|
|||
}
|
||||
|
||||
if is_active {
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", buffer);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", buffer);
|
||||
|
||||
if switched_pad && !buffer.flags().contains(gst::BufferFlags::DISCONT) {
|
||||
let buffer = buffer.make_mut();
|
||||
|
@ -173,7 +172,7 @@ impl PadSinkHandler for InputSelectorPadSinkHandler {
|
|||
list: gst::BufferList,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: pad, "Handling buffer list {:?}", list);
|
||||
gst::log!(CAT, obj = pad, "Handling buffer list {:?}", list);
|
||||
// TODO: Ideally we would keep the list intact and forward it in one go
|
||||
for buffer in list.iter_owned() {
|
||||
self.handle_item(&pad, &elem, buffer).await?;
|
||||
|
@ -230,14 +229,14 @@ impl PadSinkHandler for InputSelectorPadSinkHandler {
|
|||
}
|
||||
|
||||
fn sink_query(self, pad: &gst::Pad, imp: &InputSelector, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling query {:?}", query);
|
||||
|
||||
if query.is_serialized() {
|
||||
// FIXME: How can we do this (drops ALLOCATION and DRAIN)?
|
||||
gst::log!(CAT, obj: pad, "Dropping serialized query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Dropping serialized query {:?}", query);
|
||||
false
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Forwarding query {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Forwarding query {:?}", query);
|
||||
imp.src_pad.gst_pad().peer_query(query)
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +249,7 @@ impl PadSrcHandler for InputSelectorPadSrcHandler {
|
|||
type ElementImpl = InputSelector;
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &InputSelector, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
use gst::QueryViewMut;
|
||||
match query.view_mut() {
|
||||
|
@ -340,9 +339,9 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
impl InputSelector {
|
||||
fn unprepare(&self) {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
*state = State::default();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,8 +416,8 @@ impl ObjectImpl for InputSelector {
|
|||
let pads = self.pads.lock().unwrap();
|
||||
let mut old_pad = None;
|
||||
if let Some(ref pad) = pad {
|
||||
if pads.sink_pads.get(pad).is_some() {
|
||||
old_pad = state.active_sinkpad.clone();
|
||||
if pads.sink_pads.contains_key(pad) {
|
||||
old_pad.clone_from(&state.active_sinkpad);
|
||||
state.active_sinkpad = Some(pad.clone());
|
||||
state.switched_pad = true;
|
||||
}
|
||||
|
@ -516,7 +515,7 @@ impl ElementImpl for InputSelector {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
if let gst::StateChange::ReadyToNull = transition {
|
||||
self.unprepare();
|
||||
|
|
|
@ -144,7 +144,7 @@ impl SinkHandler {
|
|||
|
||||
// For resetting if seqnum discontinuities
|
||||
fn reset(&self, inner: &mut SinkHandlerInner, jb: &JitterBuffer) -> BTreeSet<GapPacket> {
|
||||
gst::info!(CAT, imp: jb, "Resetting");
|
||||
gst::info!(CAT, imp = jb, "Resetting");
|
||||
|
||||
let mut state = jb.state.lock().unwrap();
|
||||
state.jbuf.flush();
|
||||
|
@ -176,17 +176,17 @@ impl SinkHandler {
|
|||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let s = caps.structure(0).ok_or(gst::FlowError::Error)?;
|
||||
|
||||
gst::debug!(CAT, imp: jb, "Parsing {:?}", caps);
|
||||
gst::debug!(CAT, imp = jb, "Parsing {:?}", caps);
|
||||
|
||||
let payload = s.get::<i32>("payload").map_err(|err| {
|
||||
gst::debug!(CAT, imp: jb, "Caps 'payload': {}", err);
|
||||
gst::debug!(CAT, imp = jb, "Caps 'payload': {}", err);
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
if pt != 0 && payload as u8 != pt {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: jb,
|
||||
imp = jb,
|
||||
"Caps 'payload' ({}) doesn't match payload type ({})",
|
||||
payload,
|
||||
pt
|
||||
|
@ -196,12 +196,12 @@ impl SinkHandler {
|
|||
|
||||
inner.last_pt = Some(pt);
|
||||
let clock_rate = s.get::<i32>("clock-rate").map_err(|err| {
|
||||
gst::debug!(CAT, imp: jb, "Caps 'clock-rate': {}", err);
|
||||
gst::debug!(CAT, imp = jb, "Caps 'clock-rate': {}", err);
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
if clock_rate <= 0 {
|
||||
gst::debug!(CAT, imp: jb, "Caps 'clock-rate' <= 0");
|
||||
gst::debug!(CAT, imp = jb, "Caps 'clock-rate' <= 0");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
state.clock_rate = Some(clock_rate as u32);
|
||||
|
@ -258,7 +258,7 @@ impl SinkHandler {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: jb,
|
||||
imp = jb,
|
||||
"Handling big gap, gap packets length: {}",
|
||||
gap_packets_length
|
||||
);
|
||||
|
@ -266,20 +266,20 @@ impl SinkHandler {
|
|||
inner.gap_packets.insert(GapPacket::new(buffer));
|
||||
|
||||
if gap_packets_length > 0 {
|
||||
let mut prev_gap_seq = std::u32::MAX;
|
||||
let mut prev_gap_seq = u32::MAX;
|
||||
let mut all_consecutive = true;
|
||||
|
||||
for gap_packet in inner.gap_packets.iter() {
|
||||
gst::log!(
|
||||
CAT,
|
||||
imp: jb,
|
||||
imp = jb,
|
||||
"Looking at gap packet with seq {}",
|
||||
gap_packet.seq,
|
||||
);
|
||||
|
||||
all_consecutive = gap_packet.pt == pt;
|
||||
|
||||
if prev_gap_seq == std::u32::MAX {
|
||||
if prev_gap_seq == u32::MAX {
|
||||
prev_gap_seq = gap_packet.seq as u32;
|
||||
} else if gst_rtp::compare_seqnum(gap_packet.seq, prev_gap_seq as u16) != -1 {
|
||||
all_consecutive = false;
|
||||
|
@ -292,7 +292,7 @@ impl SinkHandler {
|
|||
}
|
||||
}
|
||||
|
||||
gst::debug!(CAT, imp: jb, "all consecutive: {}", all_consecutive);
|
||||
gst::debug!(CAT, imp = jb, "all consecutive: {}", all_consecutive);
|
||||
|
||||
if all_consecutive && gap_packets_length > 3 {
|
||||
reset = true;
|
||||
|
@ -334,7 +334,7 @@ impl SinkHandler {
|
|||
|
||||
gst::log!(
|
||||
CAT,
|
||||
imp: jb,
|
||||
imp = jb,
|
||||
"Storing buffer, seq: {}, rtptime: {}, pt: {}",
|
||||
seq,
|
||||
rtptime,
|
||||
|
@ -367,7 +367,7 @@ impl SinkHandler {
|
|||
inner.last_pt = Some(pt);
|
||||
state.clock_rate = None;
|
||||
|
||||
gst::debug!(CAT, obj: pad, "New payload type: {}", pt);
|
||||
gst::debug!(CAT, obj = pad, "New payload type: {}", pt);
|
||||
|
||||
if let Some(caps) = pad.current_caps() {
|
||||
/* Ignore errors at this point, as we want to emit request-pt-map */
|
||||
|
@ -381,7 +381,7 @@ impl SinkHandler {
|
|||
let caps = element
|
||||
.emit_by_name::<Option<gst::Caps>>("request-pt-map", &[&(pt as u32)])
|
||||
.ok_or_else(|| {
|
||||
gst::error!(CAT, obj: pad, "Signal 'request-pt-map' returned None");
|
||||
gst::error!(CAT, obj = pad, "Signal 'request-pt-map' returned None");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
let mut state = jb.state.lock().unwrap();
|
||||
|
@ -404,7 +404,7 @@ impl SinkHandler {
|
|||
if pts.is_none() {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: jb,
|
||||
imp = jb,
|
||||
"cannot calculate a valid pts for #{}, discard",
|
||||
seq
|
||||
);
|
||||
|
@ -437,7 +437,7 @@ impl SinkHandler {
|
|||
|
||||
if gap <= 0 {
|
||||
state.stats.num_late += 1;
|
||||
gst::debug!(CAT, imp: jb, "Dropping late {}", seq);
|
||||
gst::debug!(CAT, imp = jb, "Dropping late {}", seq);
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ impl SinkHandler {
|
|||
(Some(earliest_pts), Some(pts)) if pts < earliest_pts => true,
|
||||
(Some(earliest_pts), Some(pts)) if pts == earliest_pts => state
|
||||
.earliest_seqnum
|
||||
.map_or(false, |earliest_seqnum| seq > earliest_seqnum),
|
||||
.is_some_and(|earliest_seqnum| seq > earliest_seqnum),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
@ -481,7 +481,7 @@ impl SinkHandler {
|
|||
state.earliest_seqnum = Some(seq);
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Stored buffer");
|
||||
gst::log!(CAT, obj = pad, "Stored buffer");
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
@ -527,11 +527,11 @@ impl SinkHandler {
|
|||
if let Some((next_wakeup, _)) = next_wakeup {
|
||||
if let Some((previous_next_wakeup, ref abort_handle)) = state.wait_handle {
|
||||
if previous_next_wakeup.is_none()
|
||||
|| next_wakeup.map_or(false, |next| previous_next_wakeup.unwrap() > next)
|
||||
|| next_wakeup.is_some_and(|next| previous_next_wakeup.unwrap() > next)
|
||||
{
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"Rescheduling for new item {} < {}",
|
||||
next_wakeup.display(),
|
||||
previous_next_wakeup.display(),
|
||||
|
@ -555,7 +555,7 @@ impl PadSinkHandler for SinkHandler {
|
|||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::debug!(CAT, obj: pad, "Handling {:?}", buffer);
|
||||
gst::debug!(CAT, obj = pad, "Handling {:?}", buffer);
|
||||
self.enqueue_item(pad, elem.imp(), Some(buffer))
|
||||
}
|
||||
.boxed()
|
||||
|
@ -564,11 +564,11 @@ impl PadSinkHandler for SinkHandler {
|
|||
fn sink_event(self, pad: &gst::Pad, jb: &JitterBuffer, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
if let Err(err) = jb.task.flush_start().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStart failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStart failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
jb,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -579,7 +579,7 @@ impl PadSinkHandler for SinkHandler {
|
|||
}
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", event);
|
||||
jb.src_pad.gst_pad().push_event(event)
|
||||
}
|
||||
|
||||
|
@ -590,7 +590,7 @@ impl PadSinkHandler for SinkHandler {
|
|||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
let jb = elem.imp();
|
||||
|
||||
|
@ -603,7 +603,7 @@ impl PadSinkHandler for SinkHandler {
|
|||
}
|
||||
EventView::FlushStop(..) => {
|
||||
if let Err(err) = jb.task.flush_stop().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStop failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStop failed {:?}", err);
|
||||
gst::element_error!(
|
||||
elem,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -626,7 +626,7 @@ impl PadSinkHandler for SinkHandler {
|
|||
|
||||
if forward {
|
||||
// FIXME: These events should really be queued up and stay in order
|
||||
gst::log!(CAT, obj: pad, "Forwarding serialized {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Forwarding serialized {:?}", event);
|
||||
jb.src_pad.push_event(event).await
|
||||
} else {
|
||||
true
|
||||
|
@ -665,7 +665,7 @@ impl SrcHandler {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: element,
|
||||
obj = element,
|
||||
"Generating lost events seq: {}, last popped seq: {:?}",
|
||||
seqnum,
|
||||
last_popped_seqnum,
|
||||
|
@ -801,11 +801,22 @@ impl SrcHandler {
|
|||
};
|
||||
|
||||
for event in lost_events {
|
||||
gst::debug!(CAT, obj: jb.src_pad.gst_pad(), "Pushing lost event {:?}", event);
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = jb.src_pad.gst_pad(),
|
||||
"Pushing lost event {:?}",
|
||||
event
|
||||
);
|
||||
let _ = jb.src_pad.push_event(event).await;
|
||||
}
|
||||
|
||||
gst::debug!(CAT, obj: jb.src_pad.gst_pad(), "Pushing {:?} with seq {:?}", buffer, seq);
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = jb.src_pad.gst_pad(),
|
||||
"Pushing {:?} with seq {:?}",
|
||||
buffer,
|
||||
seq
|
||||
);
|
||||
|
||||
jb.src_pad.push(buffer).await
|
||||
}
|
||||
|
@ -824,7 +835,7 @@ impl SrcHandler {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: element,
|
||||
obj = element,
|
||||
"Now is {}, EOS {}, earliest pts is {}, packet_spacing {} and latency {}",
|
||||
now.display(),
|
||||
state.eos,
|
||||
|
@ -834,7 +845,7 @@ impl SrcHandler {
|
|||
);
|
||||
|
||||
if state.eos {
|
||||
gst::debug!(CAT, obj: element, "EOS, not waiting");
|
||||
gst::debug!(CAT, obj = element, "EOS, not waiting");
|
||||
return (now, Some((now, Duration::ZERO)));
|
||||
}
|
||||
|
||||
|
@ -854,7 +865,7 @@ impl SrcHandler {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: element,
|
||||
obj = element,
|
||||
"Next wakeup at {} with delay {}",
|
||||
next_wakeup.display(),
|
||||
delay
|
||||
|
@ -870,12 +881,12 @@ impl PadSrcHandler for SrcHandler {
|
|||
fn src_event(self, pad: &gst::Pad, jb: &JitterBuffer, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::FlushStart(..) => {
|
||||
if let Err(err) = jb.task.flush_start().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStart failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStart failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
jb,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -887,7 +898,7 @@ impl PadSrcHandler for SrcHandler {
|
|||
}
|
||||
EventView::FlushStop(..) => {
|
||||
if let Err(err) = jb.task.flush_stop().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStop failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStop failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
jb,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -900,14 +911,14 @@ impl PadSrcHandler for SrcHandler {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", event);
|
||||
jb.sink_pad.gst_pad().push_event(event)
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, jb: &JitterBuffer, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", query);
|
||||
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Latency(q) => {
|
||||
|
@ -1030,7 +1041,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Starting task");
|
||||
gst::log!(CAT, obj = self.element, "Starting task");
|
||||
|
||||
self.src_pad_handler.clear();
|
||||
self.sink_pad_handler.clear();
|
||||
|
@ -1043,7 +1054,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
state.jbuf.set_delay(latency);
|
||||
*jb.state.lock().unwrap() = state;
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task started");
|
||||
gst::log!(CAT, obj = self.element, "Task started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -1103,9 +1114,9 @@ impl TaskImpl for JitterBufferTask {
|
|||
|
||||
// Got aborted, reschedule if needed
|
||||
if let Some(delay_fut) = delay_fut {
|
||||
gst::debug!(CAT, obj: self.element, "Waiting");
|
||||
gst::debug!(CAT, obj = self.element, "Waiting");
|
||||
if let Err(Aborted) = delay_fut.await {
|
||||
gst::debug!(CAT, obj: self.element, "Waiting aborted");
|
||||
gst::debug!(CAT, obj = self.element, "Waiting aborted");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -1123,7 +1134,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: self.element,
|
||||
obj = self.element,
|
||||
"Woke up at {}, earliest_pts {}",
|
||||
now.display(),
|
||||
state.earliest_pts.display()
|
||||
|
@ -1166,7 +1177,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
context_wait,
|
||||
);
|
||||
if let Some((Some(next_wakeup), _)) = next_wakeup {
|
||||
if now.map_or(false, |now| next_wakeup > now) {
|
||||
if now.is_some_and(|now| next_wakeup > now) {
|
||||
// Reschedule and wait a bit longer in the next iteration
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -1179,13 +1190,13 @@ impl TaskImpl for JitterBufferTask {
|
|||
if let Err(err) = res {
|
||||
match err {
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing EOS event");
|
||||
gst::debug!(CAT, obj = self.element, "Pushing EOS event");
|
||||
let _ = jb.src_pad.push_event(gst::event::Eos::new()).await;
|
||||
}
|
||||
gst::FlowError::Flushing => {
|
||||
gst::debug!(CAT, obj: self.element, "Flushing")
|
||||
gst::debug!(CAT, obj = self.element, "Flushing")
|
||||
}
|
||||
err => gst::error!(CAT, obj: self.element, "Error {}", err),
|
||||
err => gst::error!(CAT, obj = self.element, "Error {}", err),
|
||||
}
|
||||
|
||||
return Err(err);
|
||||
|
@ -1201,7 +1212,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task");
|
||||
|
||||
let jb = self.element.imp();
|
||||
let mut jb_state = jb.state.lock().unwrap();
|
||||
|
@ -1215,7 +1226,7 @@ impl TaskImpl for JitterBufferTask {
|
|||
|
||||
*jb_state = State::default();
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -1242,7 +1253,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
|
||||
impl JitterBuffer {
|
||||
fn clear_pt_map(&self) {
|
||||
gst::debug!(CAT, imp: self, "Clearing PT map");
|
||||
gst::debug!(CAT, imp = self, "Clearing PT map");
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.clock_rate = None;
|
||||
|
@ -1250,7 +1261,7 @@ impl JitterBuffer {
|
|||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let context = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
@ -1264,28 +1275,28 @@ impl JitterBuffer {
|
|||
)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
gst::debug!(CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1528,7 +1539,7 @@ impl ElementImpl for JitterBuffer {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -91,7 +91,7 @@ impl RTPJitterBufferItem {
|
|||
r#type: 0,
|
||||
dts: dts.into().into_glib(),
|
||||
pts: pts.into().into_glib(),
|
||||
seqnum: seqnum.map(|s| s as u32).unwrap_or(std::u32::MAX),
|
||||
seqnum: seqnum.map(|s| s as u32).unwrap_or(u32::MAX),
|
||||
count: 1,
|
||||
rtptime,
|
||||
},
|
||||
|
@ -138,7 +138,7 @@ impl RTPJitterBufferItem {
|
|||
pub fn seqnum(&self) -> Option<u16> {
|
||||
unsafe {
|
||||
let item = self.0.as_ref().expect("Invalid wrapper");
|
||||
if item.as_ref().seqnum == std::u32::MAX {
|
||||
if item.as_ref().seqnum == u32::MAX {
|
||||
None
|
||||
} else {
|
||||
Some(item.as_ref().seqnum as u16)
|
||||
|
@ -306,7 +306,7 @@ impl RTPJitterBuffer {
|
|||
let pts = from_glib(pts.assume_init());
|
||||
let seqnum = seqnum.assume_init();
|
||||
|
||||
let seqnum = if seqnum == std::u32::MAX {
|
||||
let seqnum = if seqnum == u32::MAX {
|
||||
None
|
||||
} else {
|
||||
Some(seqnum as u16)
|
||||
|
@ -339,7 +339,7 @@ impl RTPJitterBuffer {
|
|||
(None, None)
|
||||
} else {
|
||||
let seqnum = (*item).seqnum;
|
||||
let seqnum = if seqnum == std::u32::MAX {
|
||||
let seqnum = if seqnum == u32::MAX {
|
||||
None
|
||||
} else {
|
||||
Some(seqnum as u16)
|
||||
|
|
|
@ -31,7 +31,6 @@ use std::collections::{HashMap, VecDeque};
|
|||
use std::sync::{Arc, Weak};
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::time::Duration;
|
||||
use std::{u32, u64};
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{Context, PadSink, PadSinkWeak, PadSrc, PadSrcWeak, Task};
|
||||
|
@ -218,7 +217,7 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::log!(SINK_CAT, obj: pad, "Handling {:?}", buffer);
|
||||
gst::log!(SINK_CAT, obj = pad, "Handling {:?}", buffer);
|
||||
let imp = elem.imp();
|
||||
imp.enqueue_item(DataQueueItem::Buffer(buffer)).await
|
||||
}
|
||||
|
@ -232,7 +231,7 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
list: gst::BufferList,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::log!(SINK_CAT, obj: pad, "Handling {:?}", list);
|
||||
gst::log!(SINK_CAT, obj = pad, "Handling {:?}", list);
|
||||
let imp = elem.imp();
|
||||
imp.enqueue_item(DataQueueItem::BufferList(list)).await
|
||||
}
|
||||
|
@ -240,7 +239,7 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
}
|
||||
|
||||
fn sink_event(self, pad: &gst::Pad, imp: &ProxySink, event: gst::Event) -> bool {
|
||||
gst::debug!(SINK_CAT, obj: pad, "Handling non-serialized {:?}", event);
|
||||
gst::debug!(SINK_CAT, obj = pad, "Handling non-serialized {:?}", event);
|
||||
|
||||
let src_pad = {
|
||||
let proxy_ctx = imp.proxy_ctx.lock().unwrap();
|
||||
|
@ -258,12 +257,12 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
}
|
||||
|
||||
if let Some(src_pad) = src_pad {
|
||||
gst::log!(SINK_CAT, obj: pad, "Forwarding non-serialized {:?}", event);
|
||||
gst::log!(SINK_CAT, obj = pad, "Forwarding non-serialized {:?}", event);
|
||||
src_pad.push_event(event)
|
||||
} else {
|
||||
gst::error!(
|
||||
SINK_CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"No src pad to forward non-serialized {:?} to",
|
||||
event
|
||||
);
|
||||
|
@ -278,7 +277,7 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
gst::log!(SINK_CAT, obj: pad, "Handling serialized {:?}", event);
|
||||
gst::log!(SINK_CAT, obj = pad, "Handling serialized {:?}", event);
|
||||
|
||||
let imp = elem.imp();
|
||||
|
||||
|
@ -291,7 +290,7 @@ impl PadSinkHandler for ProxySinkPadHandler {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
gst::log!(SINK_CAT, obj: pad, "Queuing serialized {:?}", event);
|
||||
gst::log!(SINK_CAT, obj = pad, "Queuing serialized {:?}", event);
|
||||
imp.enqueue_item(DataQueueItem::Event(event)).await.is_ok()
|
||||
}
|
||||
.boxed()
|
||||
|
@ -320,7 +319,7 @@ impl ProxySink {
|
|||
let proxy_ctx = self.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
|
||||
gst::log!(SINK_CAT, imp: self, "Trying to empty pending queue");
|
||||
gst::log!(SINK_CAT, imp = self, "Trying to empty pending queue");
|
||||
|
||||
let ProxyContextInner {
|
||||
pending_queue: ref mut pq,
|
||||
|
@ -345,7 +344,7 @@ impl ProxySink {
|
|||
|
||||
receiver
|
||||
} else {
|
||||
gst::log!(SINK_CAT, imp: self, "Pending queue is empty now");
|
||||
gst::log!(SINK_CAT, imp = self, "Pending queue is empty now");
|
||||
*pq = None;
|
||||
return;
|
||||
}
|
||||
|
@ -356,13 +355,13 @@ impl ProxySink {
|
|||
receiver
|
||||
}
|
||||
} else {
|
||||
gst::log!(SINK_CAT, imp: self, "Flushing, dropping pending queue");
|
||||
gst::log!(SINK_CAT, imp = self, "Flushing, dropping pending queue");
|
||||
*pq = None;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
gst::log!(SINK_CAT, imp: self, "Waiting for more queue space");
|
||||
gst::log!(SINK_CAT, imp = self, "Waiting for more queue space");
|
||||
let _ = more_queue_space_receiver.await;
|
||||
}
|
||||
}
|
||||
|
@ -432,18 +431,18 @@ impl ProxySink {
|
|||
|
||||
gst::log!(
|
||||
SINK_CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Proxy is full - Pushing first item on pending queue"
|
||||
);
|
||||
|
||||
if schedule_now {
|
||||
gst::log!(SINK_CAT, imp: self, "Scheduling pending queue now");
|
||||
gst::log!(SINK_CAT, imp = self, "Scheduling pending queue now");
|
||||
pending_queue.scheduled = true;
|
||||
|
||||
let wait_fut = self.schedule_pending_queue();
|
||||
Some(wait_fut)
|
||||
} else {
|
||||
gst::log!(SINK_CAT, imp: self, "Scheduling pending queue later");
|
||||
gst::log!(SINK_CAT, imp = self, "Scheduling pending queue later");
|
||||
|
||||
None
|
||||
}
|
||||
|
@ -463,7 +462,7 @@ impl ProxySink {
|
|||
};
|
||||
|
||||
if let Some(wait_fut) = wait_fut {
|
||||
gst::log!(SINK_CAT, imp: self, "Blocking until queue has space again");
|
||||
gst::log!(SINK_CAT, imp = self, "Blocking until queue has space again");
|
||||
wait_fut.await;
|
||||
}
|
||||
|
||||
|
@ -473,7 +472,7 @@ impl ProxySink {
|
|||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SINK_CAT, imp: self, "Preparing");
|
||||
gst::debug!(SINK_CAT, imp = self, "Preparing");
|
||||
|
||||
let proxy_context = self.settings.lock().unwrap().proxy_context.to_string();
|
||||
|
||||
|
@ -492,22 +491,22 @@ impl ProxySink {
|
|||
|
||||
*self.proxy_ctx.lock().unwrap() = Some(proxy_ctx);
|
||||
|
||||
gst::debug!(SINK_CAT, imp: self, "Prepared");
|
||||
gst::debug!(SINK_CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(SINK_CAT, imp = self, "Unpreparing");
|
||||
*self.proxy_ctx.lock().unwrap() = None;
|
||||
gst::debug!(SINK_CAT, imp: self, "Unprepared");
|
||||
gst::debug!(SINK_CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let proxy_ctx = self.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
|
||||
gst::debug!(SINK_CAT, imp: self, "Starting");
|
||||
gst::debug!(SINK_CAT, imp = self, "Starting");
|
||||
|
||||
{
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
@ -517,19 +516,19 @@ impl ProxySink {
|
|||
|
||||
shared_ctx.last_res = Ok(gst::FlowSuccess::Ok);
|
||||
|
||||
gst::debug!(SINK_CAT, imp: self, "Started");
|
||||
gst::debug!(SINK_CAT, imp = self, "Started");
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let proxy_ctx = self.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
|
||||
gst::debug!(SINK_CAT, imp: self, "Stopping");
|
||||
gst::debug!(SINK_CAT, imp = self, "Stopping");
|
||||
|
||||
let _ = shared_ctx.pending_queue.take();
|
||||
shared_ctx.last_res = Err(gst::FlowError::Flushing);
|
||||
|
||||
gst::debug!(SINK_CAT, imp: self, "Stopped");
|
||||
gst::debug!(SINK_CAT, imp = self, "Stopped");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -632,7 +631,7 @@ impl ElementImpl for ProxySink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(SINK_CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(SINK_CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
@ -667,7 +666,7 @@ impl PadSrcHandler for ProxySrcPadHandler {
|
|||
type ElementImpl = ProxySrc;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &ProxySrc, event: gst::Event) -> bool {
|
||||
gst::log!(SRC_CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
let sink_pad = {
|
||||
let proxy_ctx = imp.proxy_ctx.lock().unwrap();
|
||||
|
@ -684,7 +683,7 @@ impl PadSrcHandler for ProxySrcPadHandler {
|
|||
match event.view() {
|
||||
EventView::FlushStart(..) => {
|
||||
if let Err(err) = imp.task.flush_start().await_maybe_on_context() {
|
||||
gst::error!(SRC_CAT, obj: pad, "FlushStart failed {:?}", err);
|
||||
gst::error!(SRC_CAT, obj = pad, "FlushStart failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
imp,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -696,7 +695,7 @@ impl PadSrcHandler for ProxySrcPadHandler {
|
|||
}
|
||||
EventView::FlushStop(..) => {
|
||||
if let Err(err) = imp.task.flush_stop().await_maybe_on_context() {
|
||||
gst::error!(SRC_CAT, obj: pad, "FlushStop failed {:?}", err);
|
||||
gst::error!(SRC_CAT, obj = pad, "FlushStop failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
imp,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -710,16 +709,16 @@ impl PadSrcHandler for ProxySrcPadHandler {
|
|||
}
|
||||
|
||||
if let Some(sink_pad) = sink_pad {
|
||||
gst::log!(SRC_CAT, obj: pad, "Forwarding {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = pad, "Forwarding {:?}", event);
|
||||
sink_pad.push_event(event)
|
||||
} else {
|
||||
gst::error!(SRC_CAT, obj: pad, "No sink pad to forward {:?} to", event);
|
||||
gst::error!(SRC_CAT, obj = pad, "No sink pad to forward {:?} to", event);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, _proxysrc: &ProxySrc, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(SRC_CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(SRC_CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
use gst::QueryViewMut;
|
||||
let ret = match query.view_mut() {
|
||||
|
@ -751,9 +750,9 @@ impl PadSrcHandler for ProxySrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(SRC_CAT, obj: pad, "Handled {:?}", query);
|
||||
gst::log!(SRC_CAT, obj = pad, "Handled {:?}", query);
|
||||
} else {
|
||||
gst::log!(SRC_CAT, obj: pad, "Didn't handle {:?}", query);
|
||||
gst::log!(SRC_CAT, obj = pad, "Didn't handle {:?}", query);
|
||||
}
|
||||
|
||||
ret
|
||||
|
@ -784,15 +783,15 @@ impl ProxySrcTask {
|
|||
|
||||
match item {
|
||||
DataQueueItem::Buffer(buffer) => {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Forwarding {:?}", buffer);
|
||||
gst::log!(SRC_CAT, obj = self.element, "Forwarding {:?}", buffer);
|
||||
proxysrc.src_pad.push(buffer).await.map(drop)
|
||||
}
|
||||
DataQueueItem::BufferList(list) => {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Forwarding {:?}", list);
|
||||
gst::log!(SRC_CAT, obj = self.element, "Forwarding {:?}", list);
|
||||
proxysrc.src_pad.push_list(list).await.map(drop)
|
||||
}
|
||||
DataQueueItem::Event(event) => {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Forwarding {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = self.element, "Forwarding {:?}", event);
|
||||
proxysrc.src_pad.push_event(event).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -805,7 +804,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Starting task");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Starting task");
|
||||
|
||||
let proxysrc = self.element.imp();
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
|
@ -819,7 +818,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
|
||||
self.dataqueue.start();
|
||||
|
||||
gst::log!(SRC_CAT, obj: self.element, "Task started");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Task started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -841,25 +840,25 @@ impl TaskImpl for ProxySrcTask {
|
|||
let proxysrc = self.element.imp();
|
||||
match res {
|
||||
Ok(()) => {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Successfully pushed item");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Successfully pushed item");
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
shared_ctx.last_res = Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
gst::debug!(SRC_CAT, obj: self.element, "Flushing");
|
||||
gst::debug!(SRC_CAT, obj = self.element, "Flushing");
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
shared_ctx.last_res = Err(gst::FlowError::Flushing);
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(SRC_CAT, obj: self.element, "EOS");
|
||||
gst::debug!(SRC_CAT, obj = self.element, "EOS");
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
|
||||
shared_ctx.last_res = Err(gst::FlowError::Eos);
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(SRC_CAT, obj: self.element, "Got error {}", err);
|
||||
gst::error!(SRC_CAT, obj = self.element, "Got error {}", err);
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -879,7 +878,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Stopping task");
|
||||
|
||||
let proxysrc = self.element.imp();
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
|
@ -894,7 +893,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
pending_queue.notify_more_queue_space();
|
||||
}
|
||||
|
||||
gst::log!(SRC_CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -902,7 +901,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
|
||||
fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Starting task flush");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Starting task flush");
|
||||
|
||||
let proxysrc = self.element.imp();
|
||||
let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
|
||||
|
@ -912,7 +911,7 @@ impl TaskImpl for ProxySrcTask {
|
|||
|
||||
shared_ctx.last_res = Err(gst::FlowError::Flushing);
|
||||
|
||||
gst::log!(SRC_CAT, obj: self.element, "Task flush started");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Task flush started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -938,7 +937,7 @@ static SRC_CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
|
||||
impl ProxySrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SRC_CAT, imp: self, "Preparing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
|
||||
|
@ -992,13 +991,13 @@ impl ProxySrc {
|
|||
.prepare(ProxySrcTask::new(self.obj().clone(), dataqueue), ts_ctx)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(SRC_CAT, imp: self, "Prepared");
|
||||
gst::debug!(SRC_CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(SRC_CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Unpreparing");
|
||||
|
||||
{
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
@ -1011,27 +1010,27 @@ impl ProxySrc {
|
|||
*self.dataqueue.lock().unwrap() = None;
|
||||
*self.proxy_ctx.lock().unwrap() = None;
|
||||
|
||||
gst::debug!(SRC_CAT, imp: self, "Unprepared");
|
||||
gst::debug!(SRC_CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SRC_CAT, imp: self, "Stopping");
|
||||
gst::debug!(SRC_CAT, imp = self, "Stopping");
|
||||
self.task.stop().await_maybe_on_context()?;
|
||||
gst::debug!(SRC_CAT, imp: self, "Stopped");
|
||||
gst::debug!(SRC_CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SRC_CAT, imp: self, "Starting");
|
||||
gst::debug!(SRC_CAT, imp = self, "Starting");
|
||||
self.task.start().await_maybe_on_context()?;
|
||||
gst::debug!(SRC_CAT, imp: self, "Started");
|
||||
gst::debug!(SRC_CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SRC_CAT, imp: self, "Pausing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(SRC_CAT, imp: self, "Paused");
|
||||
gst::debug!(SRC_CAT, imp = self, "Paused");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1191,7 +1190,7 @@ impl ElementImpl for ProxySrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(SRC_CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(SRC_CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -30,7 +30,6 @@ use once_cell::sync::Lazy;
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::{u32, u64};
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{Context, PadSink, PadSrc, Task};
|
||||
|
@ -90,7 +89,7 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", buffer);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", buffer);
|
||||
let imp = elem.imp();
|
||||
imp.enqueue_item(DataQueueItem::Buffer(buffer)).await
|
||||
}
|
||||
|
@ -104,7 +103,7 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
list: gst::BufferList,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", list);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", list);
|
||||
let imp = elem.imp();
|
||||
imp.enqueue_item(DataQueueItem::BufferList(list)).await
|
||||
}
|
||||
|
@ -112,11 +111,11 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
}
|
||||
|
||||
fn sink_event(self, pad: &gst::Pad, imp: &Queue, event: gst::Event) -> bool {
|
||||
gst::debug!(CAT, obj: pad, "Handling non-serialized {:?}", event);
|
||||
gst::debug!(CAT, obj = pad, "Handling non-serialized {:?}", event);
|
||||
|
||||
if let gst::EventView::FlushStart(..) = event.view() {
|
||||
if let Err(err) = imp.task.flush_start().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStart failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStart failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
imp,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -127,7 +126,7 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
}
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding non-serialized {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Forwarding non-serialized {:?}", event);
|
||||
imp.src_pad.gst_pad().push_event(event)
|
||||
}
|
||||
|
||||
|
@ -138,13 +137,13 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: pad, "Handling serialized {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling serialized {:?}", event);
|
||||
|
||||
let imp = elem.imp();
|
||||
|
||||
if let gst::EventView::FlushStop(..) = event.view() {
|
||||
if let Err(err) = imp.task.flush_stop().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStop failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStop failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
imp,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -155,21 +154,21 @@ impl PadSinkHandler for QueuePadSinkHandler {
|
|||
}
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Queuing serialized {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Queuing serialized {:?}", event);
|
||||
imp.enqueue_item(DataQueueItem::Event(event)).await.is_ok()
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_query(self, pad: &gst::Pad, imp: &Queue, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
if query.is_serialized() {
|
||||
// FIXME: How can we do this?
|
||||
gst::log!(CAT, obj: pad, "Dropping serialized {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Dropping serialized {:?}", query);
|
||||
false
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", query);
|
||||
imp.src_pad.gst_pad().peer_query(query)
|
||||
}
|
||||
}
|
||||
|
@ -182,18 +181,18 @@ impl PadSrcHandler for QueuePadSrcHandler {
|
|||
type ElementImpl = Queue;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &Queue, event: gst::Event) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
use gst::EventView;
|
||||
match event.view() {
|
||||
EventView::FlushStart(..) => {
|
||||
if let Err(err) = imp.task.flush_start().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStart failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStart failed {:?}", err);
|
||||
}
|
||||
}
|
||||
EventView::FlushStop(..) => {
|
||||
if let Err(err) = imp.task.flush_stop().await_maybe_on_context() {
|
||||
gst::error!(CAT, obj: pad, "FlushStop failed {:?}", err);
|
||||
gst::error!(CAT, obj = pad, "FlushStop failed {:?}", err);
|
||||
gst::element_imp_error!(
|
||||
imp,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -206,12 +205,12 @@ impl PadSrcHandler for QueuePadSrcHandler {
|
|||
_ => (),
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", event);
|
||||
imp.sink_pad.gst_pad().push_event(event)
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &Queue, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
if let gst::QueryViewMut::Scheduling(q) = query.view_mut() {
|
||||
let mut new_query = gst::query::Scheduling::new();
|
||||
|
@ -220,7 +219,7 @@ impl PadSrcHandler for QueuePadSrcHandler {
|
|||
return res;
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Upstream returned {:?}", new_query);
|
||||
gst::log!(CAT, obj = pad, "Upstream returned {:?}", new_query);
|
||||
|
||||
let (flags, min, max, align) = new_query.result();
|
||||
q.set(flags, min, max, align);
|
||||
|
@ -232,11 +231,11 @@ impl PadSrcHandler for QueuePadSrcHandler {
|
|||
.filter(|m| m != &gst::PadMode::Pull)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
gst::log!(CAT, obj: pad, "Returning {:?}", q.query_mut());
|
||||
gst::log!(CAT, obj = pad, "Returning {:?}", q.query_mut());
|
||||
return true;
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: pad, "Forwarding {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Forwarding {:?}", query);
|
||||
imp.sink_pad.gst_pad().peer_query(query)
|
||||
}
|
||||
}
|
||||
|
@ -261,15 +260,15 @@ impl QueueTask {
|
|||
|
||||
match item {
|
||||
DataQueueItem::Buffer(buffer) => {
|
||||
gst::log!(CAT, obj: self.element, "Forwarding {:?}", buffer);
|
||||
gst::log!(CAT, obj = self.element, "Forwarding {:?}", buffer);
|
||||
queue.src_pad.push(buffer).await.map(drop)
|
||||
}
|
||||
DataQueueItem::BufferList(list) => {
|
||||
gst::log!(CAT, obj: self.element, "Forwarding {:?}", list);
|
||||
gst::log!(CAT, obj = self.element, "Forwarding {:?}", list);
|
||||
queue.src_pad.push_list(list).await.map(drop)
|
||||
}
|
||||
DataQueueItem::Event(event) => {
|
||||
gst::log!(CAT, obj: self.element, "Forwarding {:?}", event);
|
||||
gst::log!(CAT, obj = self.element, "Forwarding {:?}", event);
|
||||
queue.src_pad.push_event(event).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -282,7 +281,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Starting task");
|
||||
gst::log!(CAT, obj = self.element, "Starting task");
|
||||
|
||||
let queue = self.element.imp();
|
||||
let mut last_res = queue.last_res.lock().unwrap();
|
||||
|
@ -291,7 +290,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
*last_res = Ok(gst::FlowSuccess::Ok);
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task started");
|
||||
gst::log!(CAT, obj = self.element, "Task started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -313,20 +312,20 @@ impl TaskImpl for QueueTask {
|
|||
let queue = self.element.imp();
|
||||
match res {
|
||||
Ok(()) => {
|
||||
gst::log!(CAT, obj: self.element, "Successfully pushed item");
|
||||
gst::log!(CAT, obj = self.element, "Successfully pushed item");
|
||||
*queue.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
gst::debug!(CAT, obj: self.element, "Flushing");
|
||||
gst::debug!(CAT, obj = self.element, "Flushing");
|
||||
*queue.last_res.lock().unwrap() = Err(gst::FlowError::Flushing);
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
*queue.last_res.lock().unwrap() = Err(gst::FlowError::Eos);
|
||||
queue.src_pad.push_event(gst::event::Eos::new()).await;
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
||||
gst::error!(CAT, obj = self.element, "Got error {}", err);
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -344,7 +343,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task");
|
||||
|
||||
let queue = self.element.imp();
|
||||
let mut last_res = queue.last_res.lock().unwrap();
|
||||
|
@ -358,7 +357,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
*last_res = Err(gst::FlowError::Flushing);
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -366,7 +365,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Starting task flush");
|
||||
gst::log!(CAT, obj = self.element, "Starting task flush");
|
||||
|
||||
let queue = self.element.imp();
|
||||
let mut last_res = queue.last_res.lock().unwrap();
|
||||
|
@ -379,7 +378,7 @@ impl TaskImpl for QueueTask {
|
|||
|
||||
*last_res = Err(gst::FlowError::Flushing);
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task flush started");
|
||||
gst::log!(CAT, obj = self.element, "Task flush started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -454,7 +453,7 @@ impl Queue {
|
|||
}
|
||||
let mut pending_queue_grd = self.pending_queue.lock().unwrap();
|
||||
|
||||
gst::log!(CAT, imp: self, "Trying to empty pending queue");
|
||||
gst::log!(CAT, imp = self, "Trying to empty pending queue");
|
||||
|
||||
if let Some(pending_queue) = pending_queue_grd.as_mut() {
|
||||
let mut failed_item = None;
|
||||
|
@ -471,17 +470,17 @@ impl Queue {
|
|||
|
||||
receiver
|
||||
} else {
|
||||
gst::log!(CAT, imp: self, "Pending queue is empty now");
|
||||
gst::log!(CAT, imp = self, "Pending queue is empty now");
|
||||
*pending_queue_grd = None;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
gst::log!(CAT, imp: self, "Flushing, dropping pending queue");
|
||||
gst::log!(CAT, imp = self, "Flushing, dropping pending queue");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
gst::log!(CAT, imp: self, "Waiting for more queue space");
|
||||
gst::log!(CAT, imp = self, "Waiting for more queue space");
|
||||
let _ = more_queue_space_receiver.await;
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +489,7 @@ impl Queue {
|
|||
let wait_fut = {
|
||||
let dataqueue = self.dataqueue.lock().unwrap();
|
||||
let dataqueue = dataqueue.as_ref().ok_or_else(|| {
|
||||
gst::error!(CAT, imp: self, "No DataQueue");
|
||||
gst::error!(CAT, imp = self, "No DataQueue");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
|
@ -519,18 +518,18 @@ impl Queue {
|
|||
|
||||
gst::log!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Queue is full - Pushing first item on pending queue"
|
||||
);
|
||||
|
||||
if schedule_now {
|
||||
gst::log!(CAT, imp: self, "Scheduling pending queue now");
|
||||
gst::log!(CAT, imp = self, "Scheduling pending queue now");
|
||||
pending_queue.as_mut().unwrap().scheduled = true;
|
||||
|
||||
let wait_fut = self.schedule_pending_queue();
|
||||
Some(wait_fut)
|
||||
} else {
|
||||
gst::log!(CAT, imp: self, "Scheduling pending queue later");
|
||||
gst::log!(CAT, imp = self, "Scheduling pending queue later");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
|
@ -543,7 +542,7 @@ impl Queue {
|
|||
};
|
||||
|
||||
if let Some(wait_fut) = wait_fut {
|
||||
gst::log!(CAT, imp: self, "Blocking until queue has space again");
|
||||
gst::log!(CAT, imp = self, "Blocking until queue has space again");
|
||||
wait_fut.await;
|
||||
}
|
||||
|
||||
|
@ -551,7 +550,7 @@ impl Queue {
|
|||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
|
||||
|
@ -589,13 +588,13 @@ impl Queue {
|
|||
.prepare(QueueTask::new(self.obj().clone(), dataqueue), context)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
gst::debug!(CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
|
||||
|
@ -604,20 +603,20 @@ impl Queue {
|
|||
|
||||
*self.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok);
|
||||
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().await_maybe_on_context()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().await_maybe_on_context()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -778,7 +777,7 @@ impl ElementImpl for Queue {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -541,7 +541,7 @@ unsafe impl<T: IoSafe + Write> IoSafe for std::io::BufWriter<T> {}
|
|||
unsafe impl<T: IoSafe + Write> IoSafe for std::io::LineWriter<T> {}
|
||||
unsafe impl<T: IoSafe + ?Sized> IoSafe for &mut T {}
|
||||
unsafe impl<T: IoSafe + ?Sized> IoSafe for Box<T> {}
|
||||
unsafe impl<T: Clone + IoSafe + ?Sized> IoSafe for std::borrow::Cow<'_, T> {}
|
||||
unsafe impl<T: Clone + IoSafe> IoSafe for std::borrow::Cow<'_, T> {}
|
||||
|
||||
impl<T: Read + Send + 'static> AsyncRead for Async<T> {
|
||||
fn poll_read(
|
||||
|
|
|
@ -57,7 +57,7 @@ const READ: usize = 0;
|
|||
const WRITE: usize = 1;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_REACTOR: RefCell<Option<Reactor>> = RefCell::new(None);
|
||||
static CURRENT_REACTOR: RefCell<Option<Reactor>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -27,7 +27,7 @@ use super::{CallOnDrop, JoinHandle, Reactor};
|
|||
use crate::runtime::RUNTIME_CAT;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_SCHEDULER: RefCell<Option<HandleWeak>> = RefCell::new(None);
|
||||
static CURRENT_SCHEDULER: RefCell<Option<HandleWeak>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -301,9 +301,7 @@ impl Scheduler {
|
|||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(HandleWeak::upgrade)
|
||||
.map_or(false, |cur| {
|
||||
std::ptr::eq(self, Arc::as_ptr(&cur.0.scheduler))
|
||||
})
|
||||
.is_some_and(|cur| std::ptr::eq(self, Arc::as_ptr(&cur.0.scheduler)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use super::CallOnDrop;
|
|||
use crate::runtime::RUNTIME_CAT;
|
||||
|
||||
thread_local! {
|
||||
static CURRENT_TASK_ID: Cell<Option<TaskId>> = Cell::new(None);
|
||||
static CURRENT_TASK_ID: Cell<Option<TaskId>> = const { Cell::new(None) };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||
|
|
|
@ -129,7 +129,7 @@ pub trait PadSrcHandler: Clone + Send + Sync + 'static {
|
|||
if pad.is_active() {
|
||||
gst::debug!(
|
||||
RUNTIME_CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"Already activated in {:?} mode ",
|
||||
pad.mode()
|
||||
);
|
||||
|
@ -137,7 +137,12 @@ pub trait PadSrcHandler: Clone + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
pad.activate_mode(gst::PadMode::Push, true).map_err(|err| {
|
||||
gst::error!(RUNTIME_CAT, obj: pad, "Error in PadSrc activate: {:?}", err);
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj = pad,
|
||||
"Error in PadSrc activate: {:?}",
|
||||
err
|
||||
);
|
||||
gst::loggable_error!(RUNTIME_CAT, "Error in PadSrc activate: {:?}", err)
|
||||
})
|
||||
}
|
||||
|
@ -153,7 +158,7 @@ pub trait PadSrcHandler: Clone + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &Self::ElementImpl, event: gst::Event) -> bool {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
let elem = imp.obj();
|
||||
// FIXME with GAT on `Self::ElementImpl`, we should be able to
|
||||
|
@ -178,13 +183,13 @@ pub trait PadSrcHandler: Clone + Send + Sync + 'static {
|
|||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &Self::ElementImpl, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", query);
|
||||
if query.is_serialized() {
|
||||
// FIXME serialized queries should be handled with the dataflow
|
||||
// but we can't return a `Future` because we couldn't honor QueryRef's lifetime
|
||||
false
|
||||
} else {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
let elem = imp.obj();
|
||||
// FIXME with GAT on `Self::ElementImpl`, we should be able to
|
||||
|
@ -217,48 +222,61 @@ impl PadSrcInner {
|
|||
}
|
||||
|
||||
pub async fn push(&self, buffer: gst::Buffer) -> Result<FlowSuccess, FlowError> {
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Pushing {:?}", buffer);
|
||||
gst::log!(RUNTIME_CAT, obj = self.gst_pad, "Pushing {:?}", buffer);
|
||||
|
||||
let success = self.gst_pad.push(buffer).map_err(|err| {
|
||||
gst::error!(RUNTIME_CAT,
|
||||
obj: self.gst_pad,
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj = self.gst_pad,
|
||||
"Failed to push Buffer to PadSrc: {:?}",
|
||||
err,
|
||||
);
|
||||
err
|
||||
})?;
|
||||
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Processing any pending sub tasks");
|
||||
gst::log!(
|
||||
RUNTIME_CAT,
|
||||
obj = self.gst_pad,
|
||||
"Processing any pending sub tasks"
|
||||
);
|
||||
Context::drain_sub_tasks().await?;
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
pub async fn push_list(&self, list: gst::BufferList) -> Result<FlowSuccess, FlowError> {
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Pushing {:?}", list);
|
||||
gst::log!(RUNTIME_CAT, obj = self.gst_pad, "Pushing {:?}", list);
|
||||
|
||||
let success = self.gst_pad.push_list(list).map_err(|err| {
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj: self.gst_pad,
|
||||
obj = self.gst_pad,
|
||||
"Failed to push BufferList to PadSrc: {:?}",
|
||||
err,
|
||||
);
|
||||
err
|
||||
})?;
|
||||
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Processing any pending sub tasks");
|
||||
gst::log!(
|
||||
RUNTIME_CAT,
|
||||
obj = self.gst_pad,
|
||||
"Processing any pending sub tasks"
|
||||
);
|
||||
Context::drain_sub_tasks().await?;
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
pub async fn push_event(&self, event: gst::Event) -> bool {
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Pushing {:?}", event);
|
||||
gst::log!(RUNTIME_CAT, obj = self.gst_pad, "Pushing {:?}", event);
|
||||
|
||||
let was_handled = self.gst_pad.push_event(event);
|
||||
|
||||
gst::log!(RUNTIME_CAT, obj: self.gst_pad, "Processing any pending sub tasks");
|
||||
gst::log!(
|
||||
RUNTIME_CAT,
|
||||
obj = self.gst_pad,
|
||||
"Processing any pending sub tasks"
|
||||
);
|
||||
if Context::drain_sub_tasks().await.is_err() {
|
||||
return false;
|
||||
}
|
||||
|
@ -365,7 +383,7 @@ impl PadSrc {
|
|||
H::ElementImpl::catch_panic_pad_function(
|
||||
parent,
|
||||
|| {
|
||||
gst::error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSrc activate");
|
||||
gst::error!(RUNTIME_CAT, obj = gst_pad, "Panic in PadSrc activate");
|
||||
Err(gst::loggable_error!(
|
||||
RUNTIME_CAT,
|
||||
"Panic in PadSrc activate"
|
||||
|
@ -383,7 +401,7 @@ impl PadSrc {
|
|||
H::ElementImpl::catch_panic_pad_function(
|
||||
parent,
|
||||
|| {
|
||||
gst::error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSrc activatemode");
|
||||
gst::error!(RUNTIME_CAT, obj = gst_pad, "Panic in PadSrc activatemode");
|
||||
Err(gst::loggable_error!(
|
||||
RUNTIME_CAT,
|
||||
"Panic in PadSrc activatemode"
|
||||
|
@ -392,7 +410,7 @@ impl PadSrc {
|
|||
move |imp| {
|
||||
gst::log!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"ActivateMode {:?}, {}",
|
||||
mode,
|
||||
active
|
||||
|
@ -401,7 +419,7 @@ impl PadSrc {
|
|||
if mode == gst::PadMode::Pull {
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"Pull mode not supported by PadSrc"
|
||||
);
|
||||
return Err(gst::loggable_error!(
|
||||
|
@ -442,7 +460,7 @@ impl PadSrc {
|
|||
} else {
|
||||
gst::fixme!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"Serialized Query not supported"
|
||||
);
|
||||
false
|
||||
|
@ -507,7 +525,7 @@ pub trait PadSinkHandler: Clone + Send + Sync + 'static {
|
|||
if pad.is_active() {
|
||||
gst::debug!(
|
||||
RUNTIME_CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"Already activated in {:?} mode ",
|
||||
pad.mode()
|
||||
);
|
||||
|
@ -517,7 +535,7 @@ pub trait PadSinkHandler: Clone + Send + Sync + 'static {
|
|||
pad.activate_mode(gst::PadMode::Push, true).map_err(|err| {
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj: pad,
|
||||
obj = pad,
|
||||
"Error in PadSink activate: {:?}",
|
||||
err
|
||||
);
|
||||
|
@ -555,7 +573,7 @@ pub trait PadSinkHandler: Clone + Send + Sync + 'static {
|
|||
|
||||
fn sink_event(self, pad: &gst::Pad, imp: &Self::ElementImpl, event: gst::Event) -> bool {
|
||||
assert!(!event.is_serialized());
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
let elem = imp.obj();
|
||||
// FIXME with GAT on `Self::ElementImpl`, we should be able to
|
||||
|
@ -581,7 +599,7 @@ pub trait PadSinkHandler: Clone + Send + Sync + 'static {
|
|||
let element = unsafe { elem.unsafe_cast::<gst::Element>() };
|
||||
|
||||
async move {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
gst::Pad::event_default(&pad, Some(&element), event)
|
||||
}
|
||||
|
@ -624,12 +642,12 @@ pub trait PadSinkHandler: Clone + Send + Sync + 'static {
|
|||
query: &mut gst::QueryRef,
|
||||
) -> bool {
|
||||
if query.is_serialized() {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Dropping {:?}", query);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Dropping {:?}", query);
|
||||
// FIXME serialized queries should be handled with the dataflow
|
||||
// but we can't return a `Future` because we couldn't honor QueryRef's lifetime
|
||||
false
|
||||
} else {
|
||||
gst::log!(RUNTIME_CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(RUNTIME_CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
let elem = imp.obj();
|
||||
// FIXME with GAT on `Self::ElementImpl`, we should be able to
|
||||
|
@ -764,7 +782,7 @@ impl PadSink {
|
|||
H::ElementImpl::catch_panic_pad_function(
|
||||
parent,
|
||||
|| {
|
||||
gst::error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSink activate");
|
||||
gst::error!(RUNTIME_CAT, obj = gst_pad, "Panic in PadSink activate");
|
||||
Err(gst::loggable_error!(
|
||||
RUNTIME_CAT,
|
||||
"Panic in PadSink activate"
|
||||
|
@ -782,7 +800,11 @@ impl PadSink {
|
|||
H::ElementImpl::catch_panic_pad_function(
|
||||
parent,
|
||||
|| {
|
||||
gst::error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSink activatemode");
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj = gst_pad,
|
||||
"Panic in PadSink activatemode"
|
||||
);
|
||||
Err(gst::loggable_error!(
|
||||
RUNTIME_CAT,
|
||||
"Panic in PadSink activatemode"
|
||||
|
@ -791,7 +813,7 @@ impl PadSink {
|
|||
move |imp| {
|
||||
gst::log!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"ActivateMode {:?}, {}",
|
||||
mode,
|
||||
active
|
||||
|
@ -800,7 +822,7 @@ impl PadSink {
|
|||
if mode == gst::PadMode::Pull {
|
||||
gst::error!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"Pull mode not supported by PadSink"
|
||||
);
|
||||
return Err(gst::loggable_error!(
|
||||
|
@ -923,7 +945,7 @@ impl PadSink {
|
|||
} else {
|
||||
gst::fixme!(
|
||||
RUNTIME_CAT,
|
||||
obj: gst_pad,
|
||||
obj = gst_pad,
|
||||
"Serialized Query not supported"
|
||||
);
|
||||
false
|
||||
|
|
|
@ -33,7 +33,10 @@ use std::net::UdpSocket;
|
|||
use crate::runtime::Async;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::os::{
|
||||
fd::BorrowedFd,
|
||||
unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
|
||||
|
@ -74,7 +77,7 @@ impl<T: SocketRead> Socket<T> {
|
|||
buffer_pool.set_active(true).map_err(|err| {
|
||||
gst::error!(
|
||||
SOCKET_CAT,
|
||||
obj: element,
|
||||
obj = element,
|
||||
"Failed to prepare socket: {}",
|
||||
err
|
||||
);
|
||||
|
@ -122,7 +125,7 @@ impl<T: SocketRead> Socket<T> {
|
|||
pub async fn try_next(
|
||||
&mut self,
|
||||
) -> Result<(gst::Buffer, Option<std::net::SocketAddr>), SocketError> {
|
||||
gst::log!(SOCKET_CAT, obj: self.element, "Trying to read data");
|
||||
gst::log!(SOCKET_CAT, obj = self.element, "Trying to read data");
|
||||
|
||||
if self.mapped_buffer.is_none() {
|
||||
match self.buffer_pool.acquire_buffer(None) {
|
||||
|
@ -130,7 +133,12 @@ impl<T: SocketRead> Socket<T> {
|
|||
self.mapped_buffer = Some(buffer.into_mapped_buffer_writable().unwrap());
|
||||
}
|
||||
Err(err) => {
|
||||
gst::debug!(SOCKET_CAT, obj: self.element, "Failed to acquire buffer {:?}", err);
|
||||
gst::debug!(
|
||||
SOCKET_CAT,
|
||||
obj = self.element,
|
||||
"Failed to acquire buffer {:?}",
|
||||
err
|
||||
);
|
||||
return Err(SocketError::Gst(err));
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +157,7 @@ impl<T: SocketRead> Socket<T> {
|
|||
// so as to display another message
|
||||
gst::debug!(
|
||||
SOCKET_CAT,
|
||||
obj: self.element,
|
||||
obj = self.element,
|
||||
"Read {} bytes at {} (clock {})",
|
||||
len,
|
||||
running_time.display(),
|
||||
|
@ -157,7 +165,7 @@ impl<T: SocketRead> Socket<T> {
|
|||
);
|
||||
running_time
|
||||
} else {
|
||||
gst::debug!(SOCKET_CAT, obj: self.element, "Read {} bytes", len);
|
||||
gst::debug!(SOCKET_CAT, obj = self.element, "Read {} bytes", len);
|
||||
gst::ClockTime::NONE
|
||||
};
|
||||
|
||||
|
@ -173,7 +181,7 @@ impl<T: SocketRead> Socket<T> {
|
|||
Ok((buffer, saddr))
|
||||
}
|
||||
Err(err) => {
|
||||
gst::debug!(SOCKET_CAT, obj: self.element, "Read error {:?}", err);
|
||||
gst::debug!(SOCKET_CAT, obj = self.element, "Read error {:?}", err);
|
||||
|
||||
Err(SocketError::Io(err))
|
||||
}
|
||||
|
@ -184,7 +192,12 @@ impl<T: SocketRead> Socket<T> {
|
|||
impl<T: SocketRead> Drop for Socket<T> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self.buffer_pool.set_active(false) {
|
||||
gst::error!(SOCKET_CAT, obj: self.element, "Failed to unprepare socket: {}", err);
|
||||
gst::error!(
|
||||
SOCKET_CAT,
|
||||
obj = self.element,
|
||||
"Failed to unprepare socket: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,8 +234,14 @@ impl GioSocketWrapper {
|
|||
}
|
||||
|
||||
#[cfg(any(
|
||||
bsd,
|
||||
linux_like,
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "aix",
|
||||
target_os = "fuchsia",
|
||||
target_os = "haiku",
|
||||
|
@ -236,18 +255,30 @@ impl GioSocketWrapper {
|
|||
|
||||
let socket = self.as_socket();
|
||||
|
||||
sockopt::set_ip_tos(socket, tos)?;
|
||||
sockopt::set_ip_tos(
|
||||
unsafe { BorrowedFd::borrow_raw(socket.as_raw_fd()) },
|
||||
tos as u8,
|
||||
)?;
|
||||
|
||||
if socket.family() == gio::SocketFamily::Ipv6 {
|
||||
sockopt::set_ipv6_tclass(socket, tos)?;
|
||||
sockopt::set_ipv6_tclass(
|
||||
unsafe { BorrowedFd::borrow_raw(socket.as_raw_fd()) },
|
||||
tos as u32,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
bsd,
|
||||
linux_like,
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "aix",
|
||||
target_os = "fuchsia",
|
||||
target_os = "haiku",
|
||||
|
|
|
@ -31,8 +31,6 @@ use std::io;
|
|||
use std::net::{IpAddr, SocketAddr, TcpStream};
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::u16;
|
||||
use std::u32;
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::task;
|
||||
|
@ -40,6 +38,8 @@ use crate::runtime::{Context, PadSrc, Task, TaskState};
|
|||
|
||||
use crate::runtime::Async;
|
||||
use crate::socket::{Socket, SocketError, SocketRead};
|
||||
use futures::channel::mpsc::{channel, Receiver, Sender};
|
||||
use futures::pin_mut;
|
||||
|
||||
const DEFAULT_HOST: Option<&str> = Some("127.0.0.1");
|
||||
const DEFAULT_PORT: i32 = 4953;
|
||||
|
@ -48,6 +48,11 @@ const DEFAULT_BLOCKSIZE: u32 = 4096;
|
|||
const DEFAULT_CONTEXT: &str = "";
|
||||
const DEFAULT_CONTEXT_WAIT: Duration = Duration::ZERO;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
event_sender: Option<Sender<gst::Event>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
host: Option<String>,
|
||||
|
@ -97,7 +102,7 @@ impl PadSrcHandler for TcpClientSrcPadHandler {
|
|||
type ElementImpl = TcpClientSrc;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &TcpClientSrc, event: gst::Event) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
use gst::EventView;
|
||||
let ret = match event.view() {
|
||||
|
@ -109,16 +114,16 @@ impl PadSrcHandler for TcpClientSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", event);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", event);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &TcpClientSrc, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
use gst::QueryViewMut;
|
||||
let ret = match query.view_mut() {
|
||||
|
@ -150,9 +155,9 @@ impl PadSrcHandler for TcpClientSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", query);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", query);
|
||||
}
|
||||
|
||||
ret
|
||||
|
@ -166,10 +171,16 @@ struct TcpClientSrcTask {
|
|||
socket: Option<Socket<TcpClientReader>>,
|
||||
need_initial_events: bool,
|
||||
need_segment: bool,
|
||||
event_receiver: Receiver<gst::Event>,
|
||||
}
|
||||
|
||||
impl TcpClientSrcTask {
|
||||
fn new(element: super::TcpClientSrc, saddr: SocketAddr, buffer_pool: gst::BufferPool) -> Self {
|
||||
fn new(
|
||||
element: super::TcpClientSrc,
|
||||
saddr: SocketAddr,
|
||||
buffer_pool: gst::BufferPool,
|
||||
event_receiver: Receiver<gst::Event>,
|
||||
) -> Self {
|
||||
TcpClientSrcTask {
|
||||
element,
|
||||
saddr,
|
||||
|
@ -177,6 +188,7 @@ impl TcpClientSrcTask {
|
|||
socket: None,
|
||||
need_initial_events: true,
|
||||
need_segment: true,
|
||||
event_receiver,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,12 +196,12 @@ impl TcpClientSrcTask {
|
|||
&mut self,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::log!(CAT, obj: self.element, "Handling {:?}", buffer);
|
||||
gst::log!(CAT, obj = self.element, "Handling {:?}", buffer);
|
||||
|
||||
let tcpclientsrc = self.element.imp();
|
||||
|
||||
if self.need_initial_events {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing initial events");
|
||||
gst::debug!(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)
|
||||
|
@ -228,20 +240,20 @@ impl TcpClientSrcTask {
|
|||
let res = tcpclientsrc.src_pad.push(buffer).await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
gst::log!(CAT, obj: self.element, "Successfully pushed buffer");
|
||||
gst::log!(CAT, obj = self.element, "Successfully pushed buffer");
|
||||
}
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
gst::debug!(CAT, obj: self.element, "Flushing");
|
||||
gst::debug!(CAT, obj = self.element, "Flushing");
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
tcpclientsrc
|
||||
.src_pad
|
||||
.push_event(gst::event::Eos::new())
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
||||
gst::error!(CAT, obj = self.element, "Got error {}", err);
|
||||
gst::element_error!(
|
||||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -260,7 +272,12 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Preparing task connecting to {:?}", self.saddr);
|
||||
gst::log!(
|
||||
CAT,
|
||||
obj = self.element,
|
||||
"Preparing task connecting to {:?}",
|
||||
self.saddr
|
||||
);
|
||||
|
||||
let socket = Async::<TcpStream>::connect(self.saddr)
|
||||
.await
|
||||
|
@ -285,7 +302,7 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
})?,
|
||||
);
|
||||
|
||||
gst::log!(CAT, obj: self.element, "Task prepared");
|
||||
gst::log!(CAT, obj = self.element, "Task prepared");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -313,21 +330,42 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<gst::Buffer, gst::FlowError>> {
|
||||
async move {
|
||||
self.socket
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.try_next()
|
||||
.await
|
||||
.map(|(buffer, _saddr)| buffer)
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, obj: self.element, "Got error {:?}", err);
|
||||
let event_fut = self.event_receiver.next().fuse();
|
||||
let socket_fut = self.socket.as_mut().unwrap().try_next().fuse();
|
||||
|
||||
pin_mut!(event_fut);
|
||||
pin_mut!(socket_fut);
|
||||
|
||||
futures::select! {
|
||||
event_res = event_fut => match event_res {
|
||||
Some(event) => {
|
||||
gst::debug!(CAT, obj = self.element, "Handling element level event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
gst::EventView::Eos(_) => Err(gst::FlowError::Eos),
|
||||
ev => {
|
||||
gst::error!(CAT, obj = self.element, "Unexpected event {ev:?} on channel");
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
gst::error!(CAT, obj = self.element, "Unexpected return on event channel");
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
},
|
||||
socket_res = socket_fut => match socket_res {
|
||||
Ok((buffer, _saddr)) => Ok(buffer),
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj = self.element, "Got error {err:#}");
|
||||
|
||||
match err {
|
||||
SocketError::Gst(err) => {
|
||||
gst::element_error!(
|
||||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
["streaming stopped, reason {err}"]
|
||||
);
|
||||
}
|
||||
SocketError::Io(err) => {
|
||||
|
@ -335,12 +373,15 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
("I/O error"),
|
||||
["streaming stopped, I/O error {}", err]
|
||||
["streaming stopped, I/O error {err}"]
|
||||
);
|
||||
}
|
||||
}
|
||||
gst::FlowError::Error
|
||||
})
|
||||
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
@ -351,9 +392,9 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task");
|
||||
self.need_initial_events = true;
|
||||
gst::log!(CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -361,13 +402,47 @@ impl TaskImpl for TcpClientSrcTask {
|
|||
|
||||
fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task flush");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task flush");
|
||||
self.need_initial_events = true;
|
||||
gst::log!(CAT, obj: self.element, "Task flush stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task flush stopped");
|
||||
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.element, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
self.element
|
||||
.imp()
|
||||
.src_pad
|
||||
.push_event(gst::event::Eos::new())
|
||||
.await;
|
||||
|
||||
task::Trigger::Stop
|
||||
}
|
||||
err => {
|
||||
gst::error!(CAT, obj = self.element, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
);
|
||||
|
||||
task::Trigger::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TcpClientSrc {
|
||||
|
@ -375,6 +450,7 @@ pub struct TcpClientSrc {
|
|||
task: Task,
|
||||
configured_caps: Mutex<Option<gst::Caps>>,
|
||||
settings: Mutex<Settings>,
|
||||
state: Mutex<State>,
|
||||
}
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
|
@ -387,7 +463,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
|
||||
impl TcpClientSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
|
||||
let context =
|
||||
|
@ -431,49 +507,59 @@ impl TcpClientSrc {
|
|||
|
||||
let saddr = SocketAddr::new(host, port as u16);
|
||||
|
||||
let (sender, receiver) = channel(1);
|
||||
|
||||
// Don't block on `prepare` as the socket connection takes time.
|
||||
// This will be performed in the background and we'll block on
|
||||
// `start` which will also ensure `prepare` completed successfully.
|
||||
let fut = self
|
||||
.task
|
||||
.prepare(
|
||||
TcpClientSrcTask::new(self.obj().clone(), saddr, buffer_pool),
|
||||
TcpClientSrcTask::new(self.obj().clone(), saddr, buffer_pool, receiver),
|
||||
context,
|
||||
)
|
||||
.check()?;
|
||||
drop(fut);
|
||||
|
||||
gst::debug!(CAT, imp: self, "Preparing asynchronously");
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.event_sender = Some(sender);
|
||||
drop(state);
|
||||
|
||||
gst::debug!(CAT, imp = self, "Preparing asynchronously");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
gst::debug!(CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
gst::debug!(CAT, imp = self, "Paused");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> TaskState {
|
||||
self.task.state()
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -491,6 +577,7 @@ impl ObjectSubclass for TcpClientSrc {
|
|||
task: Task::default(),
|
||||
configured_caps: Default::default(),
|
||||
settings: Default::default(),
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -626,7 +713,7 @@ impl ElementImpl for TcpClientSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
@ -664,4 +751,31 @@ impl ElementImpl for TcpClientSrc {
|
|||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
fn send_event(&self, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::debug!(CAT, imp = self, "Handling element level event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
if self.state() != TaskState::Started {
|
||||
if let Err(err) = self.start() {
|
||||
gst::error!(CAT, imp = self, "Failed to start task thread {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if self.state() == TaskState::Started {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
if let Some(event_tx) = state.event_sender.as_mut() {
|
||||
return event_tx.try_send(event.clone()).is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => self.parent_send_event(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,6 @@ use std::collections::BTreeSet;
|
|||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use std::u16;
|
||||
use std::u8;
|
||||
|
||||
const DEFAULT_HOST: Option<&str> = Some("127.0.0.1");
|
||||
const DEFAULT_PORT: i32 = 5004;
|
||||
|
@ -202,17 +200,17 @@ impl UdpSinkPadHandler {
|
|||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
if inner.clients.contains(&addr) {
|
||||
gst::warning!(CAT, imp: imp, "Not adding client {addr:?} again");
|
||||
gst::warning!(CAT, imp = imp, "Not adding client {addr:?} again");
|
||||
return;
|
||||
}
|
||||
|
||||
match inner.configure_client(&addr) {
|
||||
Ok(()) => {
|
||||
gst::info!(CAT, imp: imp, "Added client {addr:?}");
|
||||
gst::info!(CAT, imp = imp, "Added client {addr:?}");
|
||||
inner.clients.insert(addr);
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: imp, "Failed to add client {addr:?}: {err}");
|
||||
gst::error!(CAT, imp = imp, "Failed to add client {addr:?}: {err}");
|
||||
imp.obj().post_error_message(err);
|
||||
}
|
||||
}
|
||||
|
@ -223,16 +221,16 @@ impl UdpSinkPadHandler {
|
|||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
if inner.clients.take(&addr).is_none() {
|
||||
gst::warning!(CAT, imp: imp, "Not removing unknown client {addr:?}");
|
||||
gst::warning!(CAT, imp = imp, "Not removing unknown client {addr:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
match inner.unconfigure_client(&addr) {
|
||||
Ok(()) => {
|
||||
gst::info!(CAT, imp: imp, "Removed client {addr:?}");
|
||||
gst::info!(CAT, imp = imp, "Removed client {addr:?}");
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: imp, "Failed to remove client {addr:?}: {err}");
|
||||
gst::error!(CAT, imp = imp, "Failed to remove client {addr:?}: {err}");
|
||||
imp.obj().post_error_message(err);
|
||||
}
|
||||
}
|
||||
|
@ -243,9 +241,9 @@ impl UdpSinkPadHandler {
|
|||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
if new_clients.is_empty() {
|
||||
gst::info!(CAT, imp: imp, "Clearing clients");
|
||||
gst::info!(CAT, imp = imp, "Clearing clients");
|
||||
} else {
|
||||
gst::info!(CAT, imp: imp, "Replacing clients");
|
||||
gst::info!(CAT, imp = imp, "Replacing clients");
|
||||
}
|
||||
|
||||
let old_clients = std::mem::take(&mut inner.clients);
|
||||
|
@ -257,19 +255,19 @@ impl UdpSinkPadHandler {
|
|||
// client is already configured
|
||||
inner.clients.insert(*addr);
|
||||
} else if let Err(err) = inner.unconfigure_client(addr) {
|
||||
gst::error!(CAT, imp: imp, "Failed to remove client {addr:?}: {err}");
|
||||
gst::error!(CAT, imp = imp, "Failed to remove client {addr:?}: {err}");
|
||||
res = Err(err);
|
||||
} else {
|
||||
gst::info!(CAT, imp: imp, "Removed client {addr:?}");
|
||||
gst::info!(CAT, imp = imp, "Removed client {addr:?}");
|
||||
}
|
||||
}
|
||||
|
||||
for addr in new_clients.into_iter() {
|
||||
if let Err(err) = inner.configure_client(&addr) {
|
||||
gst::error!(CAT, imp: imp, "Failed to add client {addr:?}: {err}");
|
||||
gst::error!(CAT, imp = imp, "Failed to add client {addr:?}: {err}");
|
||||
res = Err(err);
|
||||
} else {
|
||||
gst::info!(CAT, imp: imp, "Added client {addr:?}");
|
||||
gst::info!(CAT, imp = imp, "Added client {addr:?}");
|
||||
inner.clients.insert(addr);
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +319,7 @@ impl PadSinkHandler for UdpSinkPadHandler {
|
|||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
gst::debug!(CAT, obj: elem, "Handling {event:?}");
|
||||
gst::debug!(CAT, obj = elem, "Handling {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
|
@ -345,7 +343,7 @@ impl PadSinkHandler for UdpSinkPadHandler {
|
|||
}
|
||||
|
||||
fn sink_event(self, _pad: &gst::Pad, imp: &UdpSink, event: gst::Event) -> bool {
|
||||
gst::debug!(CAT, imp: imp, "Handling {event:?}");
|
||||
gst::debug!(CAT, imp = imp, "Handling {event:?}");
|
||||
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
block_on_or_add_sub_task(async move {
|
||||
|
@ -556,7 +554,7 @@ impl UdpSinkPadHandlerInner {
|
|||
};
|
||||
|
||||
if let Some(socket) = socket.as_mut() {
|
||||
gst::log!(CAT, obj: elem, "Sending to {client:?}");
|
||||
gst::log!(CAT, obj = elem, "Sending to {client:?}");
|
||||
socket.send_to(&data, *client).await.map_err(|err| {
|
||||
gst::element_error!(
|
||||
elem,
|
||||
|
@ -577,7 +575,7 @@ impl UdpSinkPadHandlerInner {
|
|||
}
|
||||
}
|
||||
|
||||
gst::log!(CAT, obj: elem, "Sent buffer {buffer:?} to all clients");
|
||||
gst::log!(CAT, obj = elem, "Sent buffer {buffer:?} to all clients");
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
|
@ -587,7 +585,7 @@ impl UdpSinkPadHandlerInner {
|
|||
let now = elem.current_running_time();
|
||||
|
||||
if let Ok(Some(delay)) = running_time.opt_checked_sub(now) {
|
||||
gst::trace!(CAT, obj: elem, "sync: waiting {delay}");
|
||||
gst::trace!(CAT, obj = elem, "sync: waiting {delay}");
|
||||
runtime::timer::delay_for(delay.into()).await;
|
||||
}
|
||||
}
|
||||
|
@ -598,7 +596,7 @@ impl UdpSinkPadHandlerInner {
|
|||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
if self.is_flushing {
|
||||
gst::info!(CAT, obj: elem, "Discarding {buffer:?} (flushing)");
|
||||
gst::info!(CAT, obj = elem, "Discarding {buffer:?} (flushing)");
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
@ -614,14 +612,14 @@ impl UdpSinkPadHandlerInner {
|
|||
self.sync(elem, rtime).await;
|
||||
|
||||
if self.is_flushing {
|
||||
gst::info!(CAT, obj: elem, "Discarding {buffer:?} (flushing)");
|
||||
gst::info!(CAT, obj = elem, "Discarding {buffer:?} (flushing)");
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::debug!(CAT, obj: elem, "Handling {buffer:?}");
|
||||
gst::debug!(CAT, obj = elem, "Handling {buffer:?}");
|
||||
|
||||
self.render(elem, buffer).await.map_err(|err| {
|
||||
element_error!(
|
||||
|
@ -700,7 +698,7 @@ impl UdpSink {
|
|||
};
|
||||
|
||||
let saddr = SocketAddr::new(bind_addr, bind_port as u16);
|
||||
gst::debug!(CAT, imp: self, "Binding to {:?}", saddr);
|
||||
gst::debug!(CAT, imp = self, "Binding to {:?}", saddr);
|
||||
|
||||
let socket = match family {
|
||||
SocketFamily::Ipv4 => socket2::Socket::new(
|
||||
|
@ -720,7 +718,7 @@ impl UdpSink {
|
|||
Err(err) => {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed to create {} socket: {}",
|
||||
match family {
|
||||
SocketFamily::Ipv4 => "IPv4",
|
||||
|
@ -773,7 +771,7 @@ impl UdpSink {
|
|||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
|
||||
|
@ -791,36 +789,36 @@ impl UdpSink {
|
|||
.prepare(self, socket, socket_v6, &settings)?;
|
||||
*self.ts_ctx.lock().unwrap() = Some(ts_ctx);
|
||||
|
||||
gst::debug!(CAT, imp: self, "Started preparation");
|
||||
gst::debug!(CAT, imp = self, "Started preparation");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
self.sink_pad_handler.unprepare();
|
||||
*self.ts_ctx.lock().unwrap() = None;
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.sink_pad_handler.stop();
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.sink_pad_handler.start();
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn try_into_socket_addr(&self, host: &str, port: i32) -> Result<SocketAddr, ()> {
|
||||
let addr: IpAddr = match host.parse() {
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to parse host {}: {}", host, err);
|
||||
gst::error!(CAT, imp = self, "Failed to parse host {}: {}", host, err);
|
||||
return Err(());
|
||||
}
|
||||
Ok(addr) => addr,
|
||||
|
@ -828,7 +826,7 @@ impl UdpSink {
|
|||
|
||||
let port: u16 = match port.try_into() {
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Invalid port {}: {}", port, err);
|
||||
gst::error!(CAT, imp = self, "Invalid port {}: {}", port, err);
|
||||
return Err(());
|
||||
}
|
||||
Ok(port) => port,
|
||||
|
@ -1090,19 +1088,19 @@ impl ObjectImpl for UdpSink {
|
|||
Err(()) => {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Invalid socket address {addr}:{port}"
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Invalid port {err}");
|
||||
gst::error!(CAT, imp = self, "Invalid port {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gst::error!(CAT, imp: self, "Invalid client {client}");
|
||||
gst::error!(CAT, imp = self, "Invalid client {client}");
|
||||
None
|
||||
}
|
||||
});
|
||||
|
@ -1217,7 +1215,7 @@ impl ElementImpl for UdpSink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -27,17 +27,17 @@ use gst_net::*;
|
|||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::i32;
|
||||
use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
use std::u16;
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{Async, Context, PadSrc, Task};
|
||||
use crate::runtime::{task, Async, Context, PadSrc, Task, TaskState};
|
||||
|
||||
use crate::socket::{wrap_socket, GioSocketWrapper, Socket, SocketError, SocketRead};
|
||||
use futures::channel::mpsc::{channel, Receiver, Sender};
|
||||
use futures::pin_mut;
|
||||
|
||||
const DEFAULT_ADDRESS: Option<&str> = Some("0.0.0.0");
|
||||
const DEFAULT_PORT: i32 = 5004;
|
||||
|
@ -49,6 +49,13 @@ const DEFAULT_USED_SOCKET: Option<GioSocketWrapper> = None;
|
|||
const DEFAULT_CONTEXT: &str = "";
|
||||
const DEFAULT_CONTEXT_WAIT: Duration = Duration::ZERO;
|
||||
const DEFAULT_RETRIEVE_SENDER_ADDRESS: bool = true;
|
||||
const DEFAULT_MULTICAST_LOOP: bool = true;
|
||||
const DEFAULT_BUFFER_SIZE: u32 = 0;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
event_sender: Option<Sender<gst::Event>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
|
@ -62,6 +69,8 @@ struct Settings {
|
|||
context: String,
|
||||
context_wait: Duration,
|
||||
retrieve_sender_address: bool,
|
||||
multicast_loop: bool,
|
||||
buffer_size: u32,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
|
@ -77,6 +86,8 @@ impl Default for Settings {
|
|||
context: DEFAULT_CONTEXT.into(),
|
||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||
retrieve_sender_address: DEFAULT_RETRIEVE_SENDER_ADDRESS,
|
||||
multicast_loop: DEFAULT_MULTICAST_LOOP,
|
||||
buffer_size: DEFAULT_BUFFER_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +125,7 @@ impl PadSrcHandler for UdpSrcPadHandler {
|
|||
type ElementImpl = UdpSrc;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &UdpSrc, event: gst::Event) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
use gst::EventView;
|
||||
let ret = match event.view() {
|
||||
|
@ -126,16 +137,16 @@ impl PadSrcHandler for UdpSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", event);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", event);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &UdpSrc, query: &mut gst::QueryRef) -> bool {
|
||||
gst::log!(CAT, obj: pad, "Handling {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handling {:?}", query);
|
||||
|
||||
use gst::QueryViewMut;
|
||||
let ret = match query.view_mut() {
|
||||
|
@ -167,9 +178,9 @@ impl PadSrcHandler for UdpSrcPadHandler {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(CAT, obj: pad, "Handled {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Handled {:?}", query);
|
||||
} else {
|
||||
gst::log!(CAT, obj: pad, "Didn't handle {:?}", query);
|
||||
gst::log!(CAT, obj = pad, "Didn't handle {:?}", query);
|
||||
}
|
||||
|
||||
ret
|
||||
|
@ -182,16 +193,18 @@ struct UdpSrcTask {
|
|||
retrieve_sender_address: bool,
|
||||
need_initial_events: bool,
|
||||
need_segment: bool,
|
||||
event_receiver: Receiver<gst::Event>,
|
||||
}
|
||||
|
||||
impl UdpSrcTask {
|
||||
fn new(element: super::UdpSrc) -> Self {
|
||||
fn new(element: super::UdpSrc, event_receiver: Receiver<gst::Event>) -> Self {
|
||||
UdpSrcTask {
|
||||
element,
|
||||
socket: None,
|
||||
retrieve_sender_address: DEFAULT_RETRIEVE_SENDER_ADDRESS,
|
||||
need_initial_events: true,
|
||||
need_segment: true,
|
||||
event_receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +217,7 @@ impl TaskImpl for UdpSrcTask {
|
|||
let udpsrc = self.element.imp();
|
||||
let mut settings = udpsrc.settings.lock().unwrap();
|
||||
|
||||
gst::debug!(CAT, obj: self.element, "Preparing Task");
|
||||
gst::debug!(CAT, obj = self.element, "Preparing Task");
|
||||
|
||||
self.retrieve_sender_address = settings.retrieve_sender_address;
|
||||
|
||||
|
@ -250,7 +263,7 @@ impl TaskImpl for UdpSrcTask {
|
|||
};
|
||||
let port = settings.port;
|
||||
|
||||
// TODO: TTL, multicast loopback, etc
|
||||
// TODO: TTL etc
|
||||
let saddr = if addr.is_multicast() {
|
||||
let bind_addr = if addr.is_ipv4() {
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
|
||||
|
@ -261,7 +274,7 @@ impl TaskImpl for UdpSrcTask {
|
|||
let saddr = SocketAddr::new(bind_addr, port as u16);
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: self.element,
|
||||
obj = self.element,
|
||||
"Binding to {:?} for multicast group {:?}",
|
||||
saddr,
|
||||
addr
|
||||
|
@ -270,7 +283,7 @@ impl TaskImpl for UdpSrcTask {
|
|||
saddr
|
||||
} else {
|
||||
let saddr = SocketAddr::new(addr, port as u16);
|
||||
gst::debug!(CAT, obj: self.element, "Binding to {:?}", saddr);
|
||||
gst::debug!(CAT, obj = self.element, "Binding to {:?}", saddr);
|
||||
|
||||
saddr
|
||||
};
|
||||
|
@ -302,6 +315,29 @@ impl TaskImpl for UdpSrcTask {
|
|||
)
|
||||
})?;
|
||||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.element,
|
||||
"socket recv buffer size is {:?}",
|
||||
socket.recv_buffer_size()
|
||||
);
|
||||
if settings.buffer_size != 0 {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = self.element,
|
||||
"changing the socket recv buffer size to {}",
|
||||
settings.buffer_size
|
||||
);
|
||||
socket
|
||||
.set_recv_buffer_size(settings.buffer_size as usize)
|
||||
.map_err(|err| {
|
||||
gst::error_msg!(
|
||||
gst::ResourceError::OpenRead,
|
||||
["Failed to set buffer_size: {}", err]
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
socket.set_reuse_port(settings.reuse).map_err(|err| {
|
||||
|
@ -339,6 +375,20 @@ impl TaskImpl for UdpSrcTask {
|
|||
["Failed to join multicast group: {}", err]
|
||||
)
|
||||
})?;
|
||||
|
||||
socket
|
||||
.as_ref()
|
||||
.set_multicast_loop_v4(settings.multicast_loop)
|
||||
.map_err(|err| {
|
||||
gst::error_msg!(
|
||||
gst::ResourceError::OpenWrite,
|
||||
[
|
||||
"Failed to set multicast loop to {}: {}",
|
||||
settings.multicast_loop,
|
||||
err
|
||||
]
|
||||
)
|
||||
})?;
|
||||
}
|
||||
IpAddr::V6(addr) => {
|
||||
socket.as_ref().join_multicast_v6(&addr, 0).map_err(|err| {
|
||||
|
@ -347,6 +397,20 @@ impl TaskImpl for UdpSrcTask {
|
|||
["Failed to join multicast group: {}", err]
|
||||
)
|
||||
})?;
|
||||
|
||||
socket
|
||||
.as_ref()
|
||||
.set_multicast_loop_v6(settings.multicast_loop)
|
||||
.map_err(|err| {
|
||||
gst::error_msg!(
|
||||
gst::ResourceError::OpenWrite,
|
||||
[
|
||||
"Failed to set multicast loop to {}: {}",
|
||||
settings.multicast_loop,
|
||||
err
|
||||
]
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +464,7 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
fn unprepare(&mut self) -> BoxFuture<'_, ()> {
|
||||
async move {
|
||||
gst::debug!(CAT, obj: self.element, "Unpreparing Task");
|
||||
gst::debug!(CAT, obj = self.element, "Unpreparing Task");
|
||||
let udpsrc = self.element.imp();
|
||||
udpsrc.settings.lock().unwrap().used_socket = None;
|
||||
self.element.notify("used-socket");
|
||||
|
@ -410,12 +474,12 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Starting task");
|
||||
gst::log!(CAT, obj = self.element, "Starting task");
|
||||
self.socket
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_clock(self.element.clock(), self.element.base_time());
|
||||
gst::log!(CAT, obj: self.element, "Task started");
|
||||
gst::log!(CAT, obj = self.element, "Task started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -423,12 +487,32 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<gst::Buffer, gst::FlowError>> {
|
||||
async move {
|
||||
self.socket
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.try_next()
|
||||
.await
|
||||
.map(|(mut buffer, saddr)| {
|
||||
let event_fut = self.event_receiver.next().fuse();
|
||||
let socket_fut = self.socket.as_mut().unwrap().try_next().fuse();
|
||||
|
||||
pin_mut!(event_fut);
|
||||
pin_mut!(socket_fut);
|
||||
|
||||
futures::select! {
|
||||
event_res = event_fut => match event_res {
|
||||
Some(event) => {
|
||||
gst::debug!(CAT, obj = self.element, "Handling element level event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
gst::EventView::Eos(_) => Err(gst::FlowError::Eos),
|
||||
ev => {
|
||||
gst::error!(CAT, obj = self.element, "Unexpected event {ev:?} on channel");
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
gst::error!(CAT, obj = self.element, "Unexpected return on event channel");
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
},
|
||||
socket_res = socket_fut => match socket_res {
|
||||
Ok((mut buffer, saddr)) => {
|
||||
if let Some(saddr) = saddr {
|
||||
if self.retrieve_sender_address {
|
||||
NetAddressMeta::add(
|
||||
|
@ -437,17 +521,19 @@ impl TaskImpl for UdpSrcTask {
|
|||
);
|
||||
}
|
||||
}
|
||||
buffer
|
||||
})
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, obj: self.element, "Got error {:?}", err);
|
||||
|
||||
Ok(buffer)
|
||||
},
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj = self.element, "Got error {err:#}");
|
||||
|
||||
match err {
|
||||
SocketError::Gst(err) => {
|
||||
gst::element_error!(
|
||||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
["streaming stopped, reason {err}"]
|
||||
);
|
||||
}
|
||||
SocketError::Io(err) => {
|
||||
|
@ -455,23 +541,26 @@ impl TaskImpl for UdpSrcTask {
|
|||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
("I/O error"),
|
||||
["streaming stopped, I/O error {}", err]
|
||||
["streaming stopped, I/O error {err}"]
|
||||
);
|
||||
}
|
||||
}
|
||||
gst::FlowError::Error
|
||||
})
|
||||
|
||||
Err(gst::FlowError::Error)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_item(&mut self, buffer: gst::Buffer) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async {
|
||||
gst::log!(CAT, obj: self.element, "Handling {:?}", buffer);
|
||||
gst::log!(CAT, obj = self.element, "Handling {:?}", buffer);
|
||||
let udpsrc = self.element.imp();
|
||||
|
||||
if self.need_initial_events {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing initial events");
|
||||
gst::debug!(CAT, obj = self.element, "Pushing initial events");
|
||||
|
||||
let stream_id =
|
||||
format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
|
||||
|
@ -502,14 +591,14 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
let res = udpsrc.src_pad.push(buffer).await.map(drop);
|
||||
match res {
|
||||
Ok(_) => gst::log!(CAT, obj: self.element, "Successfully pushed buffer"),
|
||||
Err(gst::FlowError::Flushing) => gst::debug!(CAT, obj: self.element, "Flushing"),
|
||||
Ok(_) => gst::log!(CAT, obj = self.element, "Successfully pushed buffer"),
|
||||
Err(gst::FlowError::Flushing) => gst::debug!(CAT, obj = self.element, "Flushing"),
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
udpsrc.src_pad.push_event(gst::event::Eos::new()).await;
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
||||
gst::error!(CAT, obj = self.element, "Got error {}", err);
|
||||
gst::element_error!(
|
||||
self.element,
|
||||
gst::StreamError::Failed,
|
||||
|
@ -526,10 +615,10 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task");
|
||||
self.need_initial_events = true;
|
||||
self.need_segment = true;
|
||||
gst::log!(CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -537,13 +626,47 @@ impl TaskImpl for UdpSrcTask {
|
|||
|
||||
fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.element, "Stopping task flush");
|
||||
gst::log!(CAT, obj = self.element, "Stopping task flush");
|
||||
self.need_segment = true;
|
||||
gst::log!(CAT, obj: self.element, "Stopped task flush");
|
||||
gst::log!(CAT, obj = self.element, "Stopped task flush");
|
||||
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.element, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj = self.element, "EOS");
|
||||
self.element
|
||||
.imp()
|
||||
.src_pad
|
||||
.push_event(gst::event::Eos::new())
|
||||
.await;
|
||||
|
||||
task::Trigger::Stop
|
||||
}
|
||||
err => {
|
||||
gst::error!(CAT, obj = self.element, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
);
|
||||
|
||||
task::Trigger::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UdpSrc {
|
||||
|
@ -551,6 +674,7 @@ pub struct UdpSrc {
|
|||
task: Task,
|
||||
configured_caps: Mutex<Option<gst::Caps>>,
|
||||
settings: Mutex<Settings>,
|
||||
state: Mutex<State>,
|
||||
}
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
|
@ -563,7 +687,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
|
||||
impl UdpSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
gst::debug!(CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let context =
|
||||
|
@ -575,42 +699,52 @@ impl UdpSrc {
|
|||
})?;
|
||||
drop(settings);
|
||||
|
||||
let (sender, receiver) = channel(1);
|
||||
|
||||
*self.configured_caps.lock().unwrap() = None;
|
||||
self.task
|
||||
.prepare(UdpSrcTask::new(self.obj().clone()), context)
|
||||
.prepare(UdpSrcTask::new(self.obj().clone(), receiver), context)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.event_sender = Some(sender);
|
||||
drop(state);
|
||||
|
||||
gst::debug!(CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(CAT, imp = self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
gst::debug!(CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
gst::debug!(CAT, imp = self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
gst::debug!(CAT, imp = self, "Stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
gst::debug!(CAT, imp = self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
gst::debug!(CAT, imp = self, "Started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
gst::debug!(CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
gst::debug!(CAT, imp = self, "Paused");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> TaskState {
|
||||
self.task.state()
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -628,6 +762,7 @@ impl ObjectSubclass for UdpSrc {
|
|||
task: Task::default(),
|
||||
configured_caps: Default::default(),
|
||||
settings: Default::default(),
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -679,6 +814,18 @@ impl ObjectImpl for UdpSrc {
|
|||
.blurb("Whether to retrieve the sender address and add it to buffers as meta. Disabling this might result in minor performance improvements in certain scenarios")
|
||||
.default_value(DEFAULT_RETRIEVE_SENDER_ADDRESS)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("loop")
|
||||
.nick("Loop")
|
||||
.blurb("Set the multicast loop parameter")
|
||||
.default_value(DEFAULT_MULTICAST_LOOP)
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("buffer-size")
|
||||
.nick("Buffer Size")
|
||||
.blurb("Size of the kernel receive buffer in bytes, 0=default")
|
||||
.maximum(u32::MAX)
|
||||
.default_value(DEFAULT_BUFFER_SIZE)
|
||||
.build(),
|
||||
|
||||
];
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
@ -745,6 +892,12 @@ impl ObjectImpl for UdpSrc {
|
|||
"retrieve-sender-address" => {
|
||||
settings.retrieve_sender_address = value.get().expect("type checked upstream");
|
||||
}
|
||||
"loop" => {
|
||||
settings.multicast_loop = value.get().expect("type checked upstream");
|
||||
}
|
||||
"buffer-size" => {
|
||||
settings.buffer_size = value.get().expect("type checked upstream");
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -770,6 +923,8 @@ impl ObjectImpl for UdpSrc {
|
|||
"context" => settings.context.to_value(),
|
||||
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
||||
"retrieve-sender-address" => settings.retrieve_sender_address.to_value(),
|
||||
"loop" => settings.multicast_loop.to_value(),
|
||||
"buffer-size" => settings.buffer_size.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -820,7 +975,7 @@ impl ElementImpl for UdpSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
@ -858,4 +1013,31 @@ impl ElementImpl for UdpSrc {
|
|||
|
||||
Ok(success)
|
||||
}
|
||||
|
||||
fn send_event(&self, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::debug!(CAT, imp = self, "Handling element level event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
if self.state() != TaskState::Started {
|
||||
if let Err(err) = self.start() {
|
||||
gst::error!(CAT, imp = self, "Failed to start task thread {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if self.state() == TaskState::Started {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
if let Some(event_tx) = state.event_sender.as_mut() {
|
||||
return event_tx.try_send(event.clone()).is_ok();
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => self.parent_send_event(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ mod imp_src {
|
|||
type ElementImpl = ElementSrcTest;
|
||||
|
||||
fn src_event(self, pad: &gst::Pad, imp: &ElementSrcTest, event: gst::Event) -> bool {
|
||||
gst::log!(SRC_CAT, obj: pad, "Handling {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = pad, "Handling {:?}", event);
|
||||
|
||||
let ret = match event.view() {
|
||||
EventView::FlushStart(..) => {
|
||||
|
@ -100,9 +100,9 @@ mod imp_src {
|
|||
};
|
||||
|
||||
if ret {
|
||||
gst::log!(SRC_CAT, obj: pad, "Handled {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = pad, "Handled {:?}", event);
|
||||
} else {
|
||||
gst::log!(SRC_CAT, obj: pad, "Didn't handle {:?}", event);
|
||||
gst::log!(SRC_CAT, obj = pad, "Didn't handle {:?}", event);
|
||||
}
|
||||
|
||||
ret
|
||||
|
@ -127,7 +127,7 @@ mod imp_src {
|
|||
while let Ok(Some(_item)) = self.receiver.try_next() {}
|
||||
}
|
||||
async fn push_item(&self, item: Item) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::debug!(SRC_CAT, obj: self.element, "Handling {:?}", item);
|
||||
gst::debug!(SRC_CAT, obj = self.element, "Handling {:?}", item);
|
||||
|
||||
let elementsrctest = self.element.imp();
|
||||
match item {
|
||||
|
@ -148,7 +148,7 @@ mod imp_src {
|
|||
fn try_next(&mut self) -> BoxFuture<'_, Result<Item, gst::FlowError>> {
|
||||
async move {
|
||||
self.receiver.next().await.ok_or_else(|| {
|
||||
gst::log!(SRC_CAT, obj: self.element, "SrcPad channel aborted");
|
||||
gst::log!(SRC_CAT, obj = self.element, "SrcPad channel aborted");
|
||||
gst::FlowError::Eos
|
||||
})
|
||||
}
|
||||
|
@ -159,9 +159,9 @@ mod imp_src {
|
|||
async move {
|
||||
let res = self.push_item(item).await.map(drop);
|
||||
match res {
|
||||
Ok(_) => gst::log!(SRC_CAT, obj: self.element, "Successfully pushed item"),
|
||||
Ok(_) => gst::log!(SRC_CAT, obj = self.element, "Successfully pushed item"),
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
gst::debug!(SRC_CAT, obj: self.element, "Flushing")
|
||||
gst::debug!(SRC_CAT, obj = self.element, "Flushing")
|
||||
}
|
||||
Err(err) => panic!("Got error {err}"),
|
||||
}
|
||||
|
@ -173,9 +173,9 @@ mod imp_src {
|
|||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Stopping task");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Stopping task");
|
||||
self.flush();
|
||||
gst::log!(SRC_CAT, obj: self.element, "Task stopped");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Task stopped");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -183,9 +183,9 @@ mod imp_src {
|
|||
|
||||
fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(SRC_CAT, obj: self.element, "Starting task flush");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Starting task flush");
|
||||
self.flush();
|
||||
gst::log!(SRC_CAT, obj: self.element, "Task flush started");
|
||||
gst::log!(SRC_CAT, obj = self.element, "Task flush started");
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
|
@ -219,7 +219,7 @@ mod imp_src {
|
|||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(SRC_CAT, imp: self, "Preparing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
let context =
|
||||
|
@ -240,36 +240,36 @@ mod imp_src {
|
|||
)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(SRC_CAT, imp: self, "Prepared");
|
||||
gst::debug!(SRC_CAT, imp = self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(SRC_CAT, imp: self, "Unpreparing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Unpreparing");
|
||||
|
||||
*self.sender.lock().unwrap() = None;
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
|
||||
gst::debug!(SRC_CAT, imp: self, "Unprepared");
|
||||
gst::debug!(SRC_CAT, imp = self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
gst::debug!(SRC_CAT, imp: self, "Stopping");
|
||||
gst::debug!(SRC_CAT, imp = self, "Stopping");
|
||||
self.task.stop().await_maybe_on_context().unwrap();
|
||||
gst::debug!(SRC_CAT, imp: self, "Stopped");
|
||||
gst::debug!(SRC_CAT, imp = self, "Stopped");
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
gst::debug!(SRC_CAT, imp: self, "Starting");
|
||||
gst::debug!(SRC_CAT, imp = self, "Starting");
|
||||
self.task.start().await_maybe_on_context().unwrap();
|
||||
gst::debug!(SRC_CAT, imp: self, "Started");
|
||||
gst::debug!(SRC_CAT, imp = self, "Started");
|
||||
}
|
||||
|
||||
fn pause(&self) {
|
||||
gst::debug!(SRC_CAT, imp: self, "Pausing");
|
||||
gst::debug!(SRC_CAT, imp = self, "Pausing");
|
||||
self.task.pause().block_on().unwrap();
|
||||
gst::debug!(SRC_CAT, imp: self, "Paused");
|
||||
gst::debug!(SRC_CAT, imp = self, "Paused");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -366,7 +366,7 @@ mod imp_src {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::log!(SRC_CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::log!(SRC_CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
@ -464,7 +464,7 @@ mod imp_sink {
|
|||
}
|
||||
|
||||
fn sink_event(self, pad: &gst::Pad, imp: &ElementSinkTest, event: gst::Event) -> bool {
|
||||
gst::debug!(SINK_CAT, obj: pad, "Handling non-serialized {:?}", event);
|
||||
gst::debug!(SINK_CAT, obj = pad, "Handling non-serialized {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::FlushStart(..) => {
|
||||
|
@ -482,7 +482,7 @@ mod imp_sink {
|
|||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
gst::log!(SINK_CAT, obj: pad, "Handling serialized {:?}", event);
|
||||
gst::log!(SINK_CAT, obj = pad, "Handling serialized {:?}", event);
|
||||
|
||||
let imp = elem.imp();
|
||||
if let EventView::FlushStop(..) = event.view() {
|
||||
|
@ -505,7 +505,7 @@ mod imp_sink {
|
|||
impl ElementSinkTest {
|
||||
async fn forward_item(&self, item: Item) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
if !self.flushing.load(Ordering::SeqCst) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Forwarding {:?}", item);
|
||||
gst::debug!(SINK_CAT, imp = self, "Forwarding {:?}", item);
|
||||
let mut sender = self
|
||||
.sender
|
||||
.lock()
|
||||
|
@ -521,7 +521,7 @@ mod imp_sink {
|
|||
} else {
|
||||
gst::debug!(
|
||||
SINK_CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Not forwarding {:?} due to flushing",
|
||||
item
|
||||
);
|
||||
|
@ -530,31 +530,31 @@ mod imp_sink {
|
|||
}
|
||||
|
||||
fn start(&self) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Starting");
|
||||
gst::debug!(SINK_CAT, imp = self, "Starting");
|
||||
self.flushing.store(false, Ordering::SeqCst);
|
||||
gst::debug!(SINK_CAT, imp: self, "Started");
|
||||
gst::debug!(SINK_CAT, imp = self, "Started");
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Stopping");
|
||||
gst::debug!(SINK_CAT, imp = self, "Stopping");
|
||||
self.flushing.store(true, Ordering::SeqCst);
|
||||
gst::debug!(SINK_CAT, imp: self, "Stopped");
|
||||
gst::debug!(SINK_CAT, imp = self, "Stopped");
|
||||
}
|
||||
|
||||
pub fn push_flush_start(&self) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Pushing FlushStart");
|
||||
gst::debug!(SINK_CAT, imp = self, "Pushing FlushStart");
|
||||
self.sink_pad
|
||||
.gst_pad()
|
||||
.push_event(gst::event::FlushStart::new());
|
||||
gst::debug!(SINK_CAT, imp: self, "FlushStart pushed");
|
||||
gst::debug!(SINK_CAT, imp = self, "FlushStart pushed");
|
||||
}
|
||||
|
||||
pub fn push_flush_stop(&self) {
|
||||
gst::debug!(SINK_CAT, imp: self, "Pushing FlushStop");
|
||||
gst::debug!(SINK_CAT, imp = self, "Pushing FlushStop");
|
||||
self.sink_pad
|
||||
.gst_pad()
|
||||
.push_event(gst::event::FlushStop::new(true));
|
||||
gst::debug!(SINK_CAT, imp: self, "FlushStop pushed");
|
||||
gst::debug!(SINK_CAT, imp = self, "FlushStop pushed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,7 +657,7 @@ mod imp_sink {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::log!(SINK_CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::log!(SINK_CAT, imp = self, "Changing state {:?}", transition);
|
||||
|
||||
if let gst::StateChange::PausedToReady = transition {
|
||||
self.stop();
|
||||
|
|
|
@ -218,7 +218,7 @@ fn multiple_contexts_proxy() {
|
|||
.name(format!("proxysrc-{pipeline_index}").as_str())
|
||||
.property(
|
||||
"context",
|
||||
&format!("context-{}", (pipeline_index as u32) % CONTEXT_NB),
|
||||
format!("context-{}", (pipeline_index as u32) % CONTEXT_NB),
|
||||
)
|
||||
.property("proxy-context", format!("proxy-{pipeline_index}"))
|
||||
.build()
|
||||
|
@ -364,7 +364,7 @@ fn eos() {
|
|||
sink.set_callbacks(
|
||||
gst_app::AppSinkCallbacks::builder()
|
||||
.new_sample(move |appsink| {
|
||||
gst::debug!(CAT, obj: appsink, "eos: pulling sample");
|
||||
gst::debug!(CAT, obj = appsink, "eos: pulling sample");
|
||||
let _ = appsink.pull_sample().unwrap();
|
||||
|
||||
sample_notifier.send(()).unwrap();
|
||||
|
@ -376,7 +376,7 @@ fn eos() {
|
|||
);
|
||||
|
||||
fn push_buffer(src: &gst::Element) -> bool {
|
||||
gst::debug!(CAT, obj: src, "eos: pushing buffer");
|
||||
gst::debug!(CAT, obj = src, "eos: pushing buffer");
|
||||
src.emit_by_name::<bool>("push-buffer", &[&gst::Buffer::from_slice(vec![0; 1024])])
|
||||
}
|
||||
|
||||
|
@ -498,7 +498,7 @@ fn premature_shutdown() {
|
|||
sink.set_callbacks(
|
||||
gst_app::AppSinkCallbacks::builder()
|
||||
.new_sample(move |appsink| {
|
||||
gst::debug!(CAT, obj: appsink, "premature_shutdown: pulling sample");
|
||||
gst::debug!(CAT, obj = appsink, "premature_shutdown: pulling sample");
|
||||
let _sample = appsink.pull_sample().unwrap();
|
||||
|
||||
appsink_sender.send(()).unwrap();
|
||||
|
@ -511,7 +511,7 @@ fn premature_shutdown() {
|
|||
fn push_buffer(src: &gst::Element, intent: &str) -> bool {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: src,
|
||||
obj = src,
|
||||
"premature_shutdown: pushing buffer {}",
|
||||
intent
|
||||
);
|
||||
|
@ -609,6 +609,8 @@ fn premature_shutdown() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
// FIXME: racy: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/250
|
||||
#[ignore]
|
||||
fn socket_play_null_play() {
|
||||
use gio::{
|
||||
prelude::SocketExt, InetAddress, InetSocketAddress, SocketFamily, SocketProtocol,
|
||||
|
|
|
@ -76,6 +76,8 @@ fn test_client_management() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
// FIXME: racy: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/250
|
||||
#[ignore]
|
||||
fn test_chain() {
|
||||
init();
|
||||
|
||||
|
|
176
meson.build
176
meson.build
|
@ -1,7 +1,7 @@
|
|||
project('gst-plugins-rs',
|
||||
'rust',
|
||||
'c',
|
||||
version: '0.12.0-alpha.1',
|
||||
version: '0.14.0-alpha.1',
|
||||
meson_version : '>= 1.1')
|
||||
|
||||
# dependencies.py needs a toml parsing module
|
||||
|
@ -86,7 +86,7 @@ if get_option('tests').allowed()
|
|||
deps += [['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check']]
|
||||
endif
|
||||
if get_option('gtk4').allowed()
|
||||
deps += [['gstreamer-gl-1.0', 'gst-plugins-base', 'gst_gl_dep', 'gstgl']]
|
||||
deps += [['gstreamer-gl-1.0', 'gst-plugins-base', 'gstgl_dep', 'gstgl', get_option('gtk4')]]
|
||||
endif
|
||||
if get_option('threadshare').allowed() or get_option('rtsp').allowed()
|
||||
deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
|
||||
|
@ -97,7 +97,7 @@ deps_cache += {'glib-2.0': glib_dep}
|
|||
|
||||
foreach d: deps
|
||||
dep = dependency(d[0], version: gst_req,
|
||||
fallback : [d[1], d[2]])
|
||||
fallback : [d[1], d[2]], required: d.get(4, true))
|
||||
set_variable(d[2], dep)
|
||||
deps_cache += {d[0]: dep}
|
||||
if dep.type_name() == 'internal'
|
||||
|
@ -118,6 +118,7 @@ plugins = {
|
|||
'spotify': {'library': 'libgstspotify'},
|
||||
|
||||
'file': {'library': 'libgstrsfile'},
|
||||
'originalbuffer': {'library': 'libgstoriginalbuffer'},
|
||||
# sodium can have an external dependency, see below
|
||||
'threadshare': {
|
||||
'library': 'libgstthreadshare',
|
||||
|
@ -144,6 +145,7 @@ plugins = {
|
|||
'library': 'libgstaws',
|
||||
'extra-deps': {'openssl': ['>=1.1']},
|
||||
},
|
||||
'mpegtslive': {'library': 'libgstmpegtslive'},
|
||||
'hlssink3': {'library': 'libgsthlssink3'},
|
||||
'ndi': {'library': 'libgstndi'},
|
||||
'onvif': {
|
||||
|
@ -170,6 +172,7 @@ plugins = {
|
|||
'library': 'libgsturiplaylistbin',
|
||||
'examples': ['playlist'],
|
||||
'features': ['clap'],
|
||||
'gst-version': '>=1.23.90',
|
||||
},
|
||||
|
||||
'cdg': {'library': 'libgstcdg'},
|
||||
|
@ -183,7 +186,7 @@ plugins = {
|
|||
},
|
||||
'dav1d': {
|
||||
'library': 'libgstdav1d',
|
||||
'extra-deps': {'dav1d': ['>=1.0', '<1.3']},
|
||||
'extra-deps': {'dav1d': ['>=1.3']},
|
||||
},
|
||||
'ffv1': {'library': 'libgstffv1'},
|
||||
'flavors': {'library': 'libgstrsflv'},
|
||||
|
@ -202,34 +205,11 @@ plugins = {
|
|||
'library': 'libgstrsvideofx',
|
||||
'extra-deps': {'cairo-gobject': []},
|
||||
},
|
||||
'gopbuffer': {'library': 'libgstgopbuffer'},
|
||||
'quinn': {'library': 'libgstquinn'},
|
||||
'speechmatics': {'library': 'libgstspeechmatics'},
|
||||
}
|
||||
|
||||
if get_option('examples').allowed()
|
||||
plugins += {
|
||||
'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'],
|
||||
},
|
||||
}
|
||||
else
|
||||
plugins += {
|
||||
'fallbackswitch': { 'library': 'libgstfallbackswitch'},
|
||||
'livesync': { 'library': 'libgstlivesync'},
|
||||
'togglerecord': { 'library': 'libgsttogglerecord'},
|
||||
}
|
||||
endif
|
||||
|
||||
# Won't build on platforms where it bundles the sources because of:
|
||||
# https://github.com/qnighy/libwebp-sys2-rs/issues/12
|
||||
# the fix is:
|
||||
|
@ -284,8 +264,8 @@ 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()
|
||||
gl_winsys = gstgl_dep.get_variable('gl_winsys').split()
|
||||
gl_platforms = gstgl_dep.get_variable('gl_platforms').split()
|
||||
if 'wayland' in gl_winsys
|
||||
gtk4_features += 'wayland'
|
||||
endif
|
||||
|
@ -301,6 +281,23 @@ if get_option('gtk4').allowed()
|
|||
gtk4_features += 'winegl'
|
||||
endif
|
||||
endif
|
||||
|
||||
gst_allocators_dep = dependency('gstreamer-allocators-1.0', version: '>=1.24', required: false)
|
||||
gtk_dep = dependency('gtk4', version: '>=4.6', required: get_option('gtk4'))
|
||||
if gtk_dep.found()
|
||||
if host_system == 'linux' and gtk_dep.version().version_compare('>=4.14') and \
|
||||
gst_allocators_dep.found() and 'wayland' in gtk4_features
|
||||
gtk4_features += 'dmabuf'
|
||||
endif
|
||||
|
||||
if gtk_dep.version().version_compare('>=4.14')
|
||||
gtk4_features += 'gtk_v4_14'
|
||||
elif gtk_dep.version().version_compare('>=4.12')
|
||||
gtk4_features += 'gtk_v4_12'
|
||||
elif gtk_dep.version().version_compare('>=4.10')
|
||||
gtk4_features += 'gtk_v4_10'
|
||||
endif
|
||||
|
||||
plugins += {
|
||||
'gtk4': {
|
||||
'library': 'libgstgtk4',
|
||||
|
@ -310,6 +307,37 @@ if get_option('gtk4').allowed()
|
|||
},
|
||||
}
|
||||
endif
|
||||
endif
|
||||
|
||||
examples_opt = get_option('examples')
|
||||
if examples_opt.allowed() and 'gtk4' in plugins
|
||||
plugins += {
|
||||
'fallbackswitch': {
|
||||
'library': 'libgstfallbackswitch',
|
||||
'examples_features': {
|
||||
'gtk-fallbackswitch': ['gtk', 'gio', 'gst-plugin-gtk4'],
|
||||
},
|
||||
},
|
||||
'livesync': {
|
||||
'library': 'libgstlivesync',
|
||||
'examples_features': {
|
||||
'gtk-livesync': ['gtk', 'gio', 'gst-plugin-gtk4'],
|
||||
}
|
||||
},
|
||||
'togglerecord': {
|
||||
'library': 'libgsttogglerecord',
|
||||
'examples_features': {
|
||||
'gtk-recording': ['gtk', 'gio', 'gst-plugin-gtk4'],
|
||||
}
|
||||
},
|
||||
}
|
||||
else
|
||||
plugins += {
|
||||
'fallbackswitch': { 'library': 'libgstfallbackswitch'},
|
||||
'livesync': { 'library': 'libgstlivesync'},
|
||||
'togglerecord': { 'library': 'libgsttogglerecord'},
|
||||
}
|
||||
endif
|
||||
|
||||
# Process plugins list
|
||||
|
||||
|
@ -373,8 +401,13 @@ endif
|
|||
|
||||
foreach plugin_name, details: plugins
|
||||
plugin_opt = get_variable(f'@plugin_name@_option', get_option(plugin_name))
|
||||
if plugin_opt.allowed()
|
||||
if not plugin_opt.allowed()
|
||||
debug(f'@plugin_name@ is disabled')
|
||||
continue
|
||||
endif
|
||||
plugin_deps_found = true
|
||||
|
||||
# Check whether we have all needed deps
|
||||
foreach dep_name, dep_ver: details.get('extra-deps', {})
|
||||
if dep_ver.length() != 0
|
||||
dep = dependency(dep_name, version: dep_ver, required: plugin_opt)
|
||||
|
@ -383,21 +416,74 @@ foreach plugin_name, details: plugins
|
|||
endif
|
||||
deps_cache += {dep_name: dep}
|
||||
if not dep.found()
|
||||
if dep_ver.length() != 0
|
||||
dep_ver_msg = ' '.join(dep_ver)
|
||||
debug(f'@plugin_name@ dependency @dep_name@ @dep_ver_msg@ not found, skipping')
|
||||
else
|
||||
debug(f'@plugin_name@ dependency @dep_name@ not found, skipping')
|
||||
endif
|
||||
plugin_deps_found = false
|
||||
break
|
||||
endif
|
||||
endforeach
|
||||
plugin_features = details.get('features', [])
|
||||
if plugin_deps_found
|
||||
if not plugin_deps_found
|
||||
continue
|
||||
endif
|
||||
|
||||
# Validate gst-plugin features
|
||||
plugin_features = details.get('features', [])
|
||||
foreach feature: plugin_features
|
||||
if feature.startswith('gst-plugin') and not packages.contains(feature)
|
||||
msg = f'@plugin_name@ required feature @feature@ not found'
|
||||
if plugin_opt.enabled()
|
||||
error(msg)
|
||||
endif
|
||||
message(msg + ', skipping')
|
||||
plugin_deps_found = false
|
||||
break
|
||||
endif
|
||||
endforeach
|
||||
if not plugin_deps_found
|
||||
continue
|
||||
endif
|
||||
if plugin_deps_found
|
||||
|
||||
# Check if we have the required GStreamer version
|
||||
if details.has_key('gst-version') and not \
|
||||
deps_cache['gstreamer-1.0'].version().version_compare(details['gst-version'])
|
||||
msg = '@0@ requires gstreamer version @1@'.format(plugin_name, details['gst-version'])
|
||||
if plugin_opt.enabled()
|
||||
error(msg)
|
||||
endif
|
||||
message(msg + ', skipping')
|
||||
continue
|
||||
endif
|
||||
|
||||
# Parse and enable examples
|
||||
plugin_examples = details.get('examples', [])
|
||||
foreach example: plugin_examples
|
||||
examples += example
|
||||
endforeach
|
||||
|
||||
plugin_examples_features = details.get('examples_features', {})
|
||||
foreach example, examples_features: plugin_examples_features
|
||||
example_deps_found = true
|
||||
foreach feature: examples_features
|
||||
if feature.startswith('gst-plugin') and not packages.contains(feature)
|
||||
msg = f'@plugin_name@ example @example@ required feature @feature@ not found'
|
||||
if plugin_opt.enabled() and examples_opt.enabled()
|
||||
error(msg)
|
||||
endif
|
||||
message(msg + ', skipping')
|
||||
example_deps_found = false
|
||||
break
|
||||
endif
|
||||
endforeach
|
||||
features += examples_features
|
||||
if example_deps_found
|
||||
examples += example
|
||||
endif
|
||||
endforeach
|
||||
|
||||
packages += f'gst-plugin-@plugin_name@'
|
||||
features += plugin_features
|
||||
extra_features = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
|
||||
|
@ -417,8 +503,6 @@ foreach plugin_name, details: plugins
|
|||
if default_library in ['static', 'both']
|
||||
output += [lib + '.' + ext_static]
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endforeach
|
||||
|
||||
feature_args = []
|
||||
|
@ -489,6 +573,16 @@ foreach plugin : plugins
|
|||
plugin_name = plugin_name.substring(3)
|
||||
endif
|
||||
|
||||
plugin_display_name = plugin_name
|
||||
if plugin_name.startswith('gst')
|
||||
plugin_display_name = plugin_name.substring(3)
|
||||
endif
|
||||
if plugin_display_name in plugin_names
|
||||
# When default_library=both plugins are duplicated.
|
||||
continue
|
||||
endif
|
||||
plugin_names += plugin_display_name
|
||||
|
||||
option_name = plugin_name.substring(3)
|
||||
if option_name.startswith('rs')
|
||||
option_name = option_name.substring(2)
|
||||
|
@ -533,13 +627,7 @@ foreach plugin : plugins
|
|||
warning('Static plugin @0@ is known to fail. It will not be included in libgstreamer-full.'.format(plugin_name))
|
||||
else
|
||||
gst_plugins += dep
|
||||
|
||||
pc_files += [plugin_name + '.pc']
|
||||
if plugin_name.startswith('gst')
|
||||
plugin_names += [plugin_name.substring(3)]
|
||||
else
|
||||
plugin_names += [plugin_name]
|
||||
endif
|
||||
endif
|
||||
endforeach
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@ option('claxon', type: 'feature', value: 'auto', description: 'Build claxon plug
|
|||
option('csound', type: 'feature', value: 'auto', description: 'Build csound plugin')
|
||||
option('lewton', type: 'feature', value: 'auto', description: 'Build lewton plugin')
|
||||
option('spotify', type: 'feature', value: 'auto', description: 'Build spotify plugin')
|
||||
option('speechmatics', type: 'feature', value: 'auto', description: 'Build speechmatics plugin')
|
||||
|
||||
# generic
|
||||
option('file', type: 'feature', value: 'auto', description: 'Build file plugin')
|
||||
option('originalbuffer', type: 'feature', value: 'auto', description: 'Build originalbuffer plugin')
|
||||
option('gopbuffer', type: 'feature', value: 'auto', description: 'Build gopbuffer plugin')
|
||||
option('sodium', type: 'feature', value: 'auto', description: 'Build sodium plugin')
|
||||
option('sodium-source', type: 'combo',
|
||||
choices: ['system', 'built-in'], value: 'built-in',
|
||||
|
@ -24,6 +27,7 @@ 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('mpegtslive', type: 'feature', value: 'auto', description: 'Build mpegtslive 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')
|
||||
|
@ -32,6 +36,7 @@ option('rtsp', type: 'feature', value: 'auto', description: 'Build rtsp plugin')
|
|||
option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin')
|
||||
option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin')
|
||||
option('webrtchttp', type: 'feature', value: 'auto', description: 'Build webrtchttp plugin')
|
||||
option('quinn', type: 'feature', value: 'auto', description: 'Build quinn plugin')
|
||||
|
||||
# text
|
||||
option('textahead', type: 'feature', value: 'auto', description: 'Build textahead plugin')
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
pub use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::io;
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait ReadBytesExtShort: io::Read {
|
||||
fn read_u16le(&mut self) -> io::Result<u16> {
|
||||
self.read_u16::<LittleEndian>()
|
||||
|
@ -76,6 +77,7 @@ pub trait ReadBytesExtShort: io::Read {
|
|||
|
||||
impl<T> ReadBytesExtShort for T where T: ReadBytesExt {}
|
||||
|
||||
#[allow(unused)]
|
||||
pub trait WriteBytesExtShort: WriteBytesExt {
|
||||
fn write_u16le(&mut self, n: u16) -> io::Result<()> {
|
||||
self.write_u16::<LittleEndian>(n)
|
||||
|
|
|
@ -311,10 +311,10 @@ impl FlvDemux {
|
|||
// gst::SchedulingFlags::SEEKABLE,
|
||||
// )
|
||||
// {
|
||||
// gst::debug!(CAT, obj: pad, "Activating in Pull mode");
|
||||
// gst::debug!(CAT, obj = pad, "Activating in Pull mode");
|
||||
// gst::PadMode::Pull
|
||||
// } else {
|
||||
gst::debug!(CAT, obj: pad, "Activating in Push mode");
|
||||
gst::debug!(CAT, obj = pad, "Activating in Push mode");
|
||||
gst::PadMode::Push
|
||||
// }
|
||||
};
|
||||
|
@ -366,7 +366,7 @@ impl FlvDemux {
|
|||
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::log!(CAT, obj: pad, "Handling event {:?}", event);
|
||||
gst::log!(CAT, obj = pad, "Handling event {:?}", event);
|
||||
match event.view() {
|
||||
EventView::Eos(..) => {
|
||||
// TODO implement
|
||||
|
@ -453,7 +453,7 @@ impl FlvDemux {
|
|||
pad: &gst::Pad,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::log!(CAT, obj: pad, "Handling buffer {:?}", buffer);
|
||||
gst::log!(CAT, obj = pad, "Handling buffer {:?}", buffer);
|
||||
|
||||
let mut adapter = self.adapter.lock().unwrap();
|
||||
adapter.push(buffer);
|
||||
|
@ -466,7 +466,7 @@ impl FlvDemux {
|
|||
let header = match self.find_header(&mut adapter) {
|
||||
Ok(header) => header,
|
||||
Err(_) => {
|
||||
gst::trace!(CAT, imp: self, "Need more data");
|
||||
gst::trace!(CAT, imp = self, "Need more data");
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
};
|
||||
|
@ -495,7 +495,7 @@ impl FlvDemux {
|
|||
} => {
|
||||
let avail = adapter.available();
|
||||
if avail == 0 {
|
||||
gst::trace!(CAT, imp: self, "Need more data");
|
||||
gst::trace!(CAT, imp = self, "Need more data");
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
let skip = cmp::min(avail, *skip_left as usize);
|
||||
|
@ -507,7 +507,7 @@ impl FlvDemux {
|
|||
|
||||
match res {
|
||||
Ok(None) => {
|
||||
gst::trace!(CAT, imp: self, "Need more data");
|
||||
gst::trace!(CAT, imp = self, "Need more data");
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
Ok(Some(events)) => {
|
||||
|
@ -534,7 +534,7 @@ impl FlvDemux {
|
|||
let data = adapter.map(9).unwrap();
|
||||
|
||||
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);
|
||||
adapter.flush(9);
|
||||
|
||||
|
@ -597,7 +597,7 @@ impl FlvDemux {
|
|||
let res = pad.push(buffer);
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Pushing buffer for stream {:?} returned {:?}",
|
||||
stream,
|
||||
res
|
||||
|
@ -687,7 +687,7 @@ impl StreamingState {
|
|||
match be_u32::<_, (_, nom::error::ErrorKind)>(&data[0..4]) {
|
||||
Err(_) => unreachable!(),
|
||||
Ok((_, previous_size)) => {
|
||||
gst::trace!(CAT, imp: imp, "Previous tag size {}", previous_size);
|
||||
gst::trace!(CAT, imp = imp, "Previous tag size {}", previous_size);
|
||||
// Nothing to do here, we just consume it for now
|
||||
}
|
||||
}
|
||||
|
@ -703,7 +703,7 @@ impl StreamingState {
|
|||
Ok((_, tag_header)) => tag_header,
|
||||
};
|
||||
|
||||
gst::trace!(CAT, imp: imp, "Parsed tag header {:?}", tag_header);
|
||||
gst::trace!(CAT, imp = imp, "Parsed tag header {:?}", tag_header);
|
||||
|
||||
drop(data);
|
||||
|
||||
|
@ -715,17 +715,17 @@ impl StreamingState {
|
|||
|
||||
match tag_header.tag_type {
|
||||
flavors::TagType::Script => {
|
||||
gst::trace!(CAT, imp: imp, "Found script tag");
|
||||
gst::trace!(CAT, imp = imp, "Found script tag");
|
||||
|
||||
Ok(self.handle_script_tag(imp, &tag_header, adapter))
|
||||
}
|
||||
flavors::TagType::Audio => {
|
||||
gst::trace!(CAT, imp: imp, "Found audio tag");
|
||||
gst::trace!(CAT, imp = imp, "Found audio tag");
|
||||
|
||||
self.handle_audio_tag(imp, &tag_header, adapter)
|
||||
}
|
||||
flavors::TagType::Video => {
|
||||
gst::trace!(CAT, imp: imp, "Found video tag");
|
||||
gst::trace!(CAT, imp = imp, "Found video tag");
|
||||
|
||||
self.handle_video_tag(imp, &tag_header, adapter)
|
||||
}
|
||||
|
@ -747,10 +747,10 @@ impl StreamingState {
|
|||
|
||||
match flavors::script_data(&data) {
|
||||
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);
|
||||
|
||||
let metadata = Metadata::new(script_data);
|
||||
gst::debug!(CAT, imp: imp, "Got metadata: {:?}", metadata);
|
||||
gst::debug!(CAT, imp = imp, "Got metadata: {:?}", metadata);
|
||||
|
||||
let audio_changed = self
|
||||
.audio
|
||||
|
@ -778,10 +778,10 @@ impl StreamingState {
|
|||
}
|
||||
}
|
||||
Ok((_, ref script_data)) => {
|
||||
gst::trace!(CAT, imp: imp, "Got script tag: {:?}", script_data);
|
||||
gst::trace!(CAT, imp = imp, "Got script tag: {:?}", script_data);
|
||||
}
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Error parsing script tag: {:?}", err);
|
||||
gst::error!(CAT, imp = imp, "Error parsing script tag: {:?}", err);
|
||||
}
|
||||
Err(nom::Err::Incomplete(_)) => {
|
||||
// ignore
|
||||
|
@ -801,7 +801,7 @@ impl StreamingState {
|
|||
) -> SmallVec<[Event; 4]> {
|
||||
let mut events = SmallVec::new();
|
||||
|
||||
gst::trace!(CAT, imp: imp, "Got audio data header: {:?}", data_header);
|
||||
gst::trace!(CAT, imp = imp, "Got audio data header: {:?}", data_header);
|
||||
|
||||
let new_audio_format =
|
||||
AudioFormat::new(data_header, &self.metadata, &self.aac_sequence_header);
|
||||
|
@ -809,7 +809,7 @@ impl StreamingState {
|
|||
if self.audio.as_ref() != Some(&new_audio_format) {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Got new audio format: {:?}",
|
||||
new_audio_format
|
||||
);
|
||||
|
@ -827,7 +827,7 @@ impl StreamingState {
|
|||
&& 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;
|
||||
events.push(Event::HaveAllStreams);
|
||||
}
|
||||
|
@ -846,7 +846,7 @@ impl StreamingState {
|
|||
adapter.flush((tag_header.data_size - 1) as usize);
|
||||
gst::warning!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Too small packet for AAC packet header {}",
|
||||
tag_header.data_size
|
||||
);
|
||||
|
@ -857,14 +857,14 @@ impl StreamingState {
|
|||
|
||||
match flavors::aac_audio_packet_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid AAC audio packet header: {:?}", err);
|
||||
gst::error!(CAT, imp = imp, "Invalid AAC audio packet header: {:?}", err);
|
||||
drop(data);
|
||||
adapter.flush((tag_header.data_size - 1) as usize);
|
||||
Ok(true)
|
||||
}
|
||||
Err(nom::Err::Incomplete(_)) => unreachable!(),
|
||||
Ok((_, header)) => {
|
||||
gst::trace!(CAT, imp: imp, "Got AAC packet header {:?}", header);
|
||||
gst::trace!(CAT, imp = imp, "Got AAC packet header {:?}", header);
|
||||
match header.packet_type {
|
||||
flavors::AACPacketType::SequenceHeader => {
|
||||
drop(data);
|
||||
|
@ -872,7 +872,7 @@ impl StreamingState {
|
|||
let buffer = adapter
|
||||
.take_buffer((tag_header.data_size - 1 - 1) as usize)
|
||||
.unwrap();
|
||||
gst::debug!(CAT, imp: imp, "Got AAC sequence header {:?}", buffer,);
|
||||
gst::debug!(CAT, imp = imp, "Got AAC sequence header {:?}", buffer,);
|
||||
|
||||
self.aac_sequence_header = Some(buffer);
|
||||
Ok(true)
|
||||
|
@ -898,7 +898,7 @@ impl StreamingState {
|
|||
let data = adapter.map(1).unwrap();
|
||||
let data_header = match flavors::audio_data_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid audio data header: {:?}", err);
|
||||
gst::error!(CAT, imp = imp, "Invalid audio data header: {:?}", err);
|
||||
drop(data);
|
||||
adapter.flush(tag_header.data_size as usize);
|
||||
return Ok(SmallVec::new());
|
||||
|
@ -943,7 +943,7 @@ impl StreamingState {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Outputting audio buffer {:?} for tag {:?}",
|
||||
buffer,
|
||||
tag_header,
|
||||
|
@ -963,7 +963,7 @@ impl StreamingState {
|
|||
) -> SmallVec<[Event; 4]> {
|
||||
let mut events = SmallVec::new();
|
||||
|
||||
gst::trace!(CAT, imp: imp, "Got video data header: {:?}", data_header);
|
||||
gst::trace!(CAT, imp = imp, "Got video data header: {:?}", data_header);
|
||||
|
||||
let new_video_format =
|
||||
VideoFormat::new(data_header, &self.metadata, &self.avc_sequence_header);
|
||||
|
@ -971,7 +971,7 @@ impl StreamingState {
|
|||
if self.video.as_ref() != Some(&new_video_format) {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Got new video format: {:?}",
|
||||
new_video_format
|
||||
);
|
||||
|
@ -989,7 +989,7 @@ impl StreamingState {
|
|||
&& 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;
|
||||
events.push(Event::HaveAllStreams);
|
||||
}
|
||||
|
@ -1008,7 +1008,7 @@ impl StreamingState {
|
|||
adapter.flush((tag_header.data_size - 1) as usize);
|
||||
gst::warning!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Too small packet for AVC packet header {}",
|
||||
tag_header.data_size
|
||||
);
|
||||
|
@ -1018,14 +1018,14 @@ impl StreamingState {
|
|||
let data = adapter.map(4).unwrap();
|
||||
match flavors::avc_video_packet_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid AVC video packet header: {:?}", err);
|
||||
gst::error!(CAT, imp = imp, "Invalid AVC video packet header: {:?}", err);
|
||||
drop(data);
|
||||
adapter.flush((tag_header.data_size - 1) as usize);
|
||||
Ok(None)
|
||||
}
|
||||
Err(nom::Err::Incomplete(_)) => unreachable!(),
|
||||
Ok((_, header)) => {
|
||||
gst::trace!(CAT, imp: imp, "Got AVC packet header {:?}", header);
|
||||
gst::trace!(CAT, imp = imp, "Got AVC packet header {:?}", header);
|
||||
match header.packet_type {
|
||||
flavors::AVCPacketType::SequenceHeader => {
|
||||
drop(data);
|
||||
|
@ -1035,7 +1035,7 @@ impl StreamingState {
|
|||
.unwrap();
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Got AVC sequence header {:?} of size {}",
|
||||
buffer,
|
||||
tag_header.data_size - 1 - 4
|
||||
|
@ -1071,7 +1071,7 @@ impl StreamingState {
|
|||
let data = adapter.map(1).unwrap();
|
||||
let data_header = match flavors::video_data_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid video data header: {:?}", err);
|
||||
gst::error!(CAT, imp = imp, "Invalid video data header: {:?}", err);
|
||||
drop(data);
|
||||
adapter.flush(tag_header.data_size as usize);
|
||||
return Ok(SmallVec::new());
|
||||
|
@ -1147,7 +1147,7 @@ impl StreamingState {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: imp,
|
||||
imp = imp,
|
||||
"Outputting video buffer {:?} for tag {:?}, keyframe: {}",
|
||||
buffer,
|
||||
tag_header,
|
||||
|
|
|
@ -14,8 +14,9 @@ gst = { workspace = true, features = ["v1_18"] }
|
|||
gst-base = { workspace = true, features = ["v1_18"] }
|
||||
gst-audio = { workspace = true, features = ["v1_18"] }
|
||||
gst-video = { workspace = true, features = ["v1_18"] }
|
||||
gst-pbutils = { workspace = true, features = ["v1_18"] }
|
||||
gst-pbutils = { workspace = true, features = ["v1_20"] }
|
||||
once_cell.workspace = true
|
||||
bitstream-io = "2.3"
|
||||
|
||||
[lib]
|
||||
name = "gstfmp4"
|
||||
|
@ -25,9 +26,10 @@ path = "src/lib.rs"
|
|||
[dev-dependencies]
|
||||
gst-app = { workspace = true, features = ["v1_18"] }
|
||||
gst-check = { workspace = true, features = ["v1_18"] }
|
||||
gst-pbutils = { workspace = true, features = ["v1_20"] }
|
||||
m3u8-rs = "5.0"
|
||||
chrono = "0.4"
|
||||
dash-mpd = { version = "0.14", default-features = false }
|
||||
chrono = "0.4.35"
|
||||
dash-mpd = { version = "0.17", default-features = false }
|
||||
quick-xml = { version = "0.31", features = ["serialize"] }
|
||||
serde = "1"
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ fn main() -> Result<(), Error> {
|
|||
drop(map);
|
||||
|
||||
// Remove the header from the buffer list
|
||||
buffer_list.make_mut().remove(0, 1);
|
||||
buffer_list.make_mut().remove(0..1);
|
||||
|
||||
// If the list is now empty then it only contained the media header and nothing
|
||||
// else.
|
||||
|
@ -179,11 +179,10 @@ fn main() -> Result<(), Error> {
|
|||
// Write the whole segment timeline out here, compressing multiple segments with
|
||||
// the same duration to a repeated segment.
|
||||
let mut segments = vec![];
|
||||
let mut write_segment =
|
||||
|start: gst::ClockTime, duration: gst::ClockTime, repeat: usize| {
|
||||
let mut write_segment = |start: gst::ClockTime, duration: u64, repeat: usize| {
|
||||
let mut s = dash_mpd::S {
|
||||
t: Some(start.mseconds() as i64),
|
||||
d: duration.mseconds() as i64,
|
||||
t: Some(start.mseconds()),
|
||||
d: duration,
|
||||
..Default::default()
|
||||
};
|
||||
if repeat > 0 {
|
||||
|
@ -201,15 +200,15 @@ fn main() -> Result<(), Error> {
|
|||
start = Some(segment.start_time);
|
||||
}
|
||||
if last_duration.is_none() {
|
||||
last_duration = Some(segment.duration);
|
||||
last_duration = Some(segment.duration.mseconds());
|
||||
}
|
||||
|
||||
// If the duration of this segment is different from the previous one then we
|
||||
// have to write out the segment now.
|
||||
if last_duration != Some(segment.duration) {
|
||||
if last_duration != Some(segment.duration.mseconds()) {
|
||||
write_segment(start.unwrap(), last_duration.unwrap(), num_segments - 1);
|
||||
start = Some(segment.start_time);
|
||||
last_duration = Some(segment.duration);
|
||||
last_duration = Some(segment.duration.mseconds());
|
||||
num_segments = 1;
|
||||
} else {
|
||||
num_segments += 1;
|
||||
|
|
|
@ -153,7 +153,7 @@ fn trim_segments(state: &mut StreamState) {
|
|||
// safe side
|
||||
removal_time: segment
|
||||
.date_time
|
||||
.checked_add_signed(Duration::seconds(20))
|
||||
.checked_add_signed(Duration::try_seconds(20).unwrap())
|
||||
.unwrap(),
|
||||
path: segment.path.clone(),
|
||||
});
|
||||
|
@ -267,7 +267,7 @@ fn setup_appsink(appsink: &gst_app::AppSink, name: &str, path: &Path, is_video:
|
|||
drop(map);
|
||||
|
||||
// Remove the header from the buffer list
|
||||
buffer_list.make_mut().remove(0, 1);
|
||||
buffer_list.make_mut().remove(0..1);
|
||||
|
||||
// If the list is now empty then it only contained the media header and nothing
|
||||
// else.
|
||||
|
@ -422,7 +422,6 @@ impl VideoStream {
|
|||
.build()?;
|
||||
let mux = gst::ElementFactory::make("cmafmux")
|
||||
.property("fragment-duration", 2500.mseconds())
|
||||
.property_from_str("header-update-mode", "update")
|
||||
.property("write-mehd", true)
|
||||
.build()?;
|
||||
let appsink = gst_app::AppSink::builder().buffer_list(true).build();
|
||||
|
|
|
@ -170,7 +170,7 @@ fn setup_appsink(appsink: &gst_app::AppSink, name: &str, path: &Path, is_video:
|
|||
drop(map);
|
||||
|
||||
// Remove the header from the buffer list
|
||||
buffer_list.make_mut().remove(0, 1);
|
||||
buffer_list.make_mut().remove(0..1);
|
||||
|
||||
// If the list is now empty then it only contained the media header and nothing
|
||||
// else.
|
||||
|
@ -360,6 +360,10 @@ impl AudioStream {
|
|||
.property("samplesperbuffer", 4410)
|
||||
.property_from_str("wave", &self.wave)
|
||||
.build()?;
|
||||
let taginject = gst::ElementFactory::make("taginject")
|
||||
.property_from_str("tags", &format!("language-code={}", self.lang))
|
||||
.property_from_str("scope", "stream")
|
||||
.build()?;
|
||||
let raw_capsfilter = gst::ElementFactory::make("capsfilter")
|
||||
.property(
|
||||
"caps",
|
||||
|
@ -374,9 +378,23 @@ impl AudioStream {
|
|||
.build()?;
|
||||
let appsink = gst_app::AppSink::builder().buffer_list(true).build();
|
||||
|
||||
pipeline.add_many([&src, &raw_capsfilter, &enc, &mux, appsink.upcast_ref()])?;
|
||||
pipeline.add_many([
|
||||
&src,
|
||||
&taginject,
|
||||
&raw_capsfilter,
|
||||
&enc,
|
||||
&mux,
|
||||
appsink.upcast_ref(),
|
||||
])?;
|
||||
|
||||
gst::Element::link_many([&src, &raw_capsfilter, &enc, &mux, appsink.upcast_ref()])?;
|
||||
gst::Element::link_many([
|
||||
&src,
|
||||
&taginject,
|
||||
&raw_capsfilter,
|
||||
&enc,
|
||||
&mux,
|
||||
appsink.upcast_ref(),
|
||||
])?;
|
||||
|
||||
probe_encoder(state, enc);
|
||||
|
||||
|
@ -416,7 +434,7 @@ fn main() -> Result<(), Error> {
|
|||
},
|
||||
AudioStream {
|
||||
name: "audio_1".to_string(),
|
||||
lang: "fre".to_string(),
|
||||
lang: "fra".to_string(),
|
||||
default: false,
|
||||
wave: "white-noise".to_string(),
|
||||
},
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
use gst::prelude::*;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Error};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use super::Buffer;
|
||||
use super::{Buffer, ImageOrientation, IDENTITY_MATRIX};
|
||||
|
||||
fn write_box<T, F: FnOnce(&mut Vec<u8>) -> Result<T, Error>>(
|
||||
vec: &mut Vec<u8>,
|
||||
|
@ -160,6 +161,13 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
"audio/mpeg" => {
|
||||
compatible_brands.push(b"caac");
|
||||
}
|
||||
"audio/x-opus" => {
|
||||
compatible_brands.push(b"opus");
|
||||
}
|
||||
"video/x-av1" => {
|
||||
compatible_brands.push(b"av01");
|
||||
compatible_brands.push(b"cmf2");
|
||||
}
|
||||
"video/x-h265" => {
|
||||
let width = s.get::<i32>("width").ok();
|
||||
let height = s.get::<i32>("height").ok();
|
||||
|
@ -577,7 +585,7 @@ fn write_trak(
|
|||
|
||||
fn write_tkhd(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
idx: usize,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
|
@ -604,9 +612,8 @@ fn write_tkhd(
|
|||
// Volume
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
match s.name().as_str() {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
v.extend((1u16 << 8).to_be_bytes())
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => v.extend((1u16 << 8).to_be_bytes()),
|
||||
_ => v.extend(0u16.to_be_bytes()),
|
||||
}
|
||||
|
||||
|
@ -614,21 +621,15 @@ fn write_tkhd(
|
|||
v.extend([0u8; 2]);
|
||||
|
||||
// Matrix
|
||||
v.extend(
|
||||
[
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(16384u32 << 16).to_be_bytes(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
let matrix = match s.name().as_str() {
|
||||
x if x.starts_with("video/") || x.starts_with("image/") => cfg
|
||||
.orientation
|
||||
.unwrap_or(ImageOrientation::Rotate0)
|
||||
.transform_matrix(),
|
||||
_ => &IDENTITY_MATRIX,
|
||||
};
|
||||
|
||||
v.extend(matrix.iter().flatten());
|
||||
|
||||
// Width/height
|
||||
match s.name().as_str() {
|
||||
|
@ -700,7 +701,6 @@ fn write_tref(
|
|||
fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 {
|
||||
let lang = lang.borrow();
|
||||
|
||||
// TODO: Need to relax this once we get the language code from tags
|
||||
assert!(lang.iter().all(u8::is_ascii_lowercase));
|
||||
|
||||
(((lang[0] as u16 - 0x60) & 0x1F) << 10)
|
||||
|
@ -710,7 +710,7 @@ fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 {
|
|||
|
||||
fn write_mdhd(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -724,8 +724,11 @@ fn write_mdhd(
|
|||
v.extend(0u64.to_be_bytes());
|
||||
|
||||
// Language as ISO-639-2/T
|
||||
// TODO: get actual language from the tags
|
||||
if let Some(lang) = cfg.language_code {
|
||||
v.extend(language_code(lang).to_be_bytes());
|
||||
} else {
|
||||
v.extend(language_code(b"und").to_be_bytes());
|
||||
}
|
||||
|
||||
// Pre-defined
|
||||
v.extend([0u8; 2]);
|
||||
|
@ -745,9 +748,8 @@ fn write_hdlr(
|
|||
let (handler_type, name) = match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
|
||||
| "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()),
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
(b"soun", b"SoundHandler\0".as_slice())
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => (b"soun", b"SoundHandler\0".as_slice()),
|
||||
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
@ -777,7 +779,8 @@ fn write_minf(
|
|||
// Flags are always 1 for unspecified reasons
|
||||
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => {
|
||||
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_smhd(v, cfg)
|
||||
})?
|
||||
|
@ -886,9 +889,8 @@ fn write_stsd(
|
|||
match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
|
||||
| "image/jpeg" => write_visual_sample_entry(v, cfg, stream)?,
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
write_audio_sample_entry(v, cfg, stream)?
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "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!(),
|
||||
}
|
||||
|
@ -1098,9 +1100,9 @@ fn write_visual_sample_entry(
|
|||
"professional" => 2,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let level = 1; // FIXME
|
||||
let tier = 0; // FIXME
|
||||
// TODO: Use `gst_codec_utils_av1_get_seq_level_idx` when exposed in bindings
|
||||
let level = av1_seq_level_idx(s.get::<&str>("level").ok());
|
||||
let tier = av1_tier(s.get::<&str>("tier").ok());
|
||||
let (high_bitdepth, twelve_bit) =
|
||||
match s.get::<u32>("bit-depth-luma").unwrap() {
|
||||
8 => (false, false),
|
||||
|
@ -1145,6 +1147,10 @@ fn write_visual_sample_entry(
|
|||
v.extend_from_slice(&codec_data);
|
||||
}
|
||||
|
||||
if let Some(extra_data) = &stream.extra_header_data {
|
||||
// configOBUs
|
||||
v.extend_from_slice(extra_data.as_slice());
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
@ -1253,6 +1259,44 @@ fn write_visual_sample_entry(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn av1_seq_level_idx(level: Option<&str>) -> u8 {
|
||||
match level {
|
||||
Some("2.0") => 0,
|
||||
Some("2.1") => 1,
|
||||
Some("2.2") => 2,
|
||||
Some("2.3") => 3,
|
||||
Some("3.0") => 4,
|
||||
Some("3.1") => 5,
|
||||
Some("3.2") => 6,
|
||||
Some("3.3") => 7,
|
||||
Some("4.0") => 8,
|
||||
Some("4.1") => 9,
|
||||
Some("4.2") => 10,
|
||||
Some("4.3") => 11,
|
||||
Some("5.0") => 12,
|
||||
Some("5.1") => 13,
|
||||
Some("5.2") => 14,
|
||||
Some("5.3") => 15,
|
||||
Some("6.0") => 16,
|
||||
Some("6.1") => 17,
|
||||
Some("6.2") => 18,
|
||||
Some("6.3") => 19,
|
||||
Some("7.0") => 20,
|
||||
Some("7.1") => 21,
|
||||
Some("7.2") => 22,
|
||||
Some("7.3") => 23,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn av1_tier(tier: Option<&str>) -> u8 {
|
||||
match tier {
|
||||
Some("main") => 0,
|
||||
Some("high") => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_audio_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
|
@ -1262,6 +1306,7 @@ fn write_audio_sample_entry(
|
|||
let fourcc = match s.name().as_str() {
|
||||
"audio/mpeg" => b"mp4a",
|
||||
"audio/x-opus" => b"Opus",
|
||||
"audio/x-flac" => b"fLaC",
|
||||
"audio/x-alaw" => b"alaw",
|
||||
"audio/x-mulaw" => b"ulaw",
|
||||
"audio/x-adpcm" => {
|
||||
|
@ -1280,6 +1325,10 @@ fn write_audio_sample_entry(
|
|||
let bitrate = s.get::<i32>("bitrate").context("no ADPCM bitrate field")?;
|
||||
(bitrate / 8000) as u16
|
||||
}
|
||||
"audio/x-flac" => with_flac_metadata(&stream.caps, |streaminfo, _| {
|
||||
1 + (u16::from_be_bytes([streaminfo[16], streaminfo[17]]) >> 4 & 0b11111)
|
||||
})
|
||||
.context("FLAC metadata error")?,
|
||||
_ => 16u16,
|
||||
};
|
||||
|
||||
|
@ -1322,6 +1371,9 @@ fn write_audio_sample_entry(
|
|||
"audio/x-opus" => {
|
||||
write_dops(v, &stream.caps)?;
|
||||
}
|
||||
"audio/x-flac" => {
|
||||
write_dfla(v, &stream.caps)?;
|
||||
}
|
||||
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
@ -1516,6 +1568,35 @@ fn write_dops(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
|
|||
})
|
||||
}
|
||||
|
||||
fn with_flac_metadata<R>(
|
||||
caps: &gst::Caps,
|
||||
cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R,
|
||||
) -> Result<R, Error> {
|
||||
let caps = caps.structure(0).unwrap();
|
||||
let header = caps.get::<gst::ArrayRef>("streamheader").unwrap();
|
||||
let (streaminfo, remainder) = header.as_ref().split_first().unwrap();
|
||||
let streaminfo = streaminfo.get::<&gst::BufferRef>().unwrap();
|
||||
let streaminfo = streaminfo.map_readable().unwrap();
|
||||
// 13 bytes for the Ogg/FLAC prefix and 38 for the streaminfo itself.
|
||||
match <&[_; 13 + 38]>::try_from(streaminfo.as_slice()) {
|
||||
Ok(i) if i.starts_with(b"\x7FFLAC\x01\x00") => Ok(cb(&i[13..], remainder)),
|
||||
Ok(_) | Err(_) => bail!("Unknown streamheader format"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_dfla(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
|
||||
write_full_box(v, b"dfLa", 0, 0, move |v| {
|
||||
with_flac_metadata(caps, |streaminfo, remainder| {
|
||||
v.extend(streaminfo);
|
||||
for metadata in remainder {
|
||||
let metadata = metadata.get::<&gst::BufferRef>().unwrap();
|
||||
let metadata = metadata.map_readable().unwrap();
|
||||
v.extend(&metadata[..]);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn write_xml_meta_data_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,8 @@ use gst::prelude::*;
|
|||
mod boxes;
|
||||
mod imp;
|
||||
|
||||
mod obu;
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct FMP4MuxPad(ObjectSubclass<imp::FMP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||
}
|
||||
|
@ -71,6 +73,80 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub(crate) enum ImageOrientation {
|
||||
Rotate0,
|
||||
Rotate90,
|
||||
Rotate180,
|
||||
Rotate270,
|
||||
// TODO:
|
||||
// FlipRotate0,
|
||||
// FlipRotate90,
|
||||
// FlipRotate180,
|
||||
// FlipRotate270,
|
||||
}
|
||||
|
||||
type TransformMatrix = [[u8; 4]; 9];
|
||||
|
||||
const IDENTITY_MATRIX: TransformMatrix = [
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_90_MATRIX: TransformMatrix = [
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_180_MATRIX: TransformMatrix = [
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_270_MATRIX: TransformMatrix = [
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
impl ImageOrientation {
|
||||
pub(crate) fn transform_matrix(&self) -> &'static TransformMatrix {
|
||||
match self {
|
||||
ImageOrientation::Rotate0 => &IDENTITY_MATRIX,
|
||||
ImageOrientation::Rotate90 => &ROTATE_90_MATRIX,
|
||||
ImageOrientation::Rotate180 => &ROTATE_180_MATRIX,
|
||||
ImageOrientation::Rotate270 => &ROTATE_270_MATRIX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HeaderConfiguration {
|
||||
variant: Variant,
|
||||
|
@ -85,6 +161,8 @@ pub(crate) struct HeaderConfiguration {
|
|||
|
||||
write_mehd: bool,
|
||||
duration: Option<gst::ClockTime>,
|
||||
language_code: Option<[u8; 3]>,
|
||||
orientation: Option<ImageOrientation>,
|
||||
|
||||
/// Start UTC time in ONVIF mode.
|
||||
/// Since Jan 1 1601 in 100ns units.
|
||||
|
@ -101,6 +179,9 @@ pub(crate) struct HeaderStream {
|
|||
|
||||
/// Pre-defined trak timescale if not 0.
|
||||
trak_timescale: u32,
|
||||
|
||||
// More data to be included in the fragmented stream header
|
||||
extra_header_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
303
mux/fmp4/src/fmp4mux/obu.rs
Normal file
303
mux/fmp4/src/fmp4mux/obu.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
//
|
||||
// Copyright (C) 2022 Vivienne Watermeier <vwatermeier@igalia.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(non_camel_case_types)]
|
||||
|
||||
use bitstream_io::{BigEndian, BitRead, BitReader, Endianness};
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
pub fn parse_leb128<R, E>(reader: &mut BitReader<R, E>) -> io::Result<(u32, u32)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
E: Endianness,
|
||||
{
|
||||
let mut value = 0;
|
||||
let mut num_bytes = 0;
|
||||
|
||||
for i in 0..8 {
|
||||
let byte = reader.read::<u32>(8)?;
|
||||
value |= (byte & 0x7f) << (i * 7);
|
||||
num_bytes += 1;
|
||||
if byte & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reader.byte_align();
|
||||
Ok((value, num_bytes))
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct SizedObu {
|
||||
pub obu_type: ObuType,
|
||||
pub has_extension: bool,
|
||||
/// If the OBU header is followed by a leb128 size field.
|
||||
pub has_size_field: bool,
|
||||
pub temporal_id: u8,
|
||||
pub spatial_id: u8,
|
||||
/// size of the OBU payload in bytes.
|
||||
/// This may refer to different sizes in different contexts, not always
|
||||
/// to the entire OBU payload as it is in the AV1 bitstream.
|
||||
pub size: u32,
|
||||
/// the number of bytes the leb128 size field will take up
|
||||
/// when written with write_leb128().
|
||||
/// This does not imply `has_size_field`, and does not necessarily match with
|
||||
/// the length of the internal size field if present.
|
||||
pub leb_size: u32,
|
||||
pub header_len: u32,
|
||||
/// indicates that only part of this OBU has been processed so far
|
||||
pub is_fragment: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ObuType {
|
||||
Reserved,
|
||||
SequenceHeader,
|
||||
TemporalDelimiter,
|
||||
FrameHeader,
|
||||
TileGroup,
|
||||
Metadata,
|
||||
Frame,
|
||||
RedundantFrameHeader,
|
||||
TileList,
|
||||
Padding,
|
||||
}
|
||||
|
||||
impl Default for ObuType {
|
||||
fn default() -> Self {
|
||||
Self::Reserved
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedObu {
|
||||
/// Parse an OBU header and size field. If the OBU is not expected to contain
|
||||
/// a size field, but the size is known from external information,
|
||||
/// parse as an `UnsizedObu` and use `to_sized`.
|
||||
pub fn parse<R, E>(reader: &mut BitReader<R, E>) -> io::Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
E: Endianness,
|
||||
{
|
||||
// check the forbidden bit
|
||||
if reader.read_bit()? {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"forbidden bit in OBU header is set",
|
||||
));
|
||||
}
|
||||
|
||||
let obu_type = reader.read::<u8>(4)?.into();
|
||||
let has_extension = reader.read_bit()?;
|
||||
|
||||
// require a size field
|
||||
if !reader.read_bit()? {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"expected a size field",
|
||||
));
|
||||
}
|
||||
|
||||
// ignore the reserved bit
|
||||
let _ = reader.read_bit()?;
|
||||
|
||||
let (temporal_id, spatial_id) = if has_extension {
|
||||
(reader.read::<u8>(3)?, reader.read::<u8>(2)?)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
reader.byte_align();
|
||||
|
||||
let (size, leb_size) = parse_leb128(reader)?;
|
||||
|
||||
Ok(Self {
|
||||
obu_type,
|
||||
has_extension,
|
||||
has_size_field: true,
|
||||
temporal_id,
|
||||
spatial_id,
|
||||
size,
|
||||
leb_size,
|
||||
header_len: has_extension as u32 + 1,
|
||||
is_fragment: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// The amount of bytes this OBU will take up, including the space needed for
|
||||
/// its leb128 size field.
|
||||
pub fn full_size(&self) -> u32 {
|
||||
self.size + self.leb_size + self.header_len
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_seq_header_obu_bytes(data: &[u8]) -> io::Result<Option<Vec<u8>>> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
while cursor.position() < data.len() as u64 {
|
||||
let obu_start = cursor.position();
|
||||
|
||||
let Ok(obu) = SizedObu::parse(&mut BitReader::endian(&mut cursor, BigEndian)) else {
|
||||
break;
|
||||
};
|
||||
|
||||
// set reader to the beginning of the OBU
|
||||
cursor.seek(SeekFrom::Start(obu_start))?;
|
||||
|
||||
if obu.obu_type != ObuType::SequenceHeader {
|
||||
// Skip the full OBU
|
||||
cursor.seek(SeekFrom::Current(obu.full_size() as i64))?;
|
||||
continue;
|
||||
};
|
||||
|
||||
// read the full OBU
|
||||
let mut bytes = vec![0; obu.full_size() as usize];
|
||||
cursor.read_exact(&mut bytes)?;
|
||||
|
||||
return Ok(Some(bytes));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
impl From<u8> for ObuType {
|
||||
fn from(n: u8) -> Self {
|
||||
assert!(n < 16);
|
||||
|
||||
match n {
|
||||
1 => Self::SequenceHeader,
|
||||
2 => Self::TemporalDelimiter,
|
||||
3 => Self::FrameHeader,
|
||||
4 => Self::TileGroup,
|
||||
5 => Self::Metadata,
|
||||
6 => Self::Frame,
|
||||
7 => Self::RedundantFrameHeader,
|
||||
8 => Self::TileList,
|
||||
15 => Self::Padding,
|
||||
_ => Self::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ObuType> for u8 {
|
||||
fn from(ty: ObuType) -> Self {
|
||||
match ty {
|
||||
ObuType::Reserved => 0,
|
||||
ObuType::SequenceHeader => 1,
|
||||
ObuType::TemporalDelimiter => 2,
|
||||
ObuType::FrameHeader => 3,
|
||||
ObuType::TileGroup => 4,
|
||||
ObuType::Metadata => 5,
|
||||
ObuType::Frame => 6,
|
||||
ObuType::RedundantFrameHeader => 7,
|
||||
ObuType::TileList => 8,
|
||||
ObuType::Padding => 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bitstream_io::{BigEndian, BitReader};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
static OBUS: Lazy<Vec<(SizedObu, Vec<u8>)>> = Lazy::new(|| {
|
||||
vec![
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::TemporalDelimiter,
|
||||
has_extension: false,
|
||||
has_size_field: true,
|
||||
temporal_id: 0,
|
||||
spatial_id: 0,
|
||||
size: 0,
|
||||
leb_size: 1,
|
||||
header_len: 1,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0001_0010, 0b0000_0000],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::Padding,
|
||||
has_extension: false,
|
||||
has_size_field: true,
|
||||
temporal_id: 0,
|
||||
spatial_id: 0,
|
||||
size: 10,
|
||||
leb_size: 1,
|
||||
header_len: 1,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0111_1010, 0b0000_1010, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::SequenceHeader,
|
||||
has_extension: true,
|
||||
has_size_field: true,
|
||||
temporal_id: 4,
|
||||
spatial_id: 3,
|
||||
size: 5,
|
||||
leb_size: 1,
|
||||
header_len: 2,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0000_1110, 0b1001_1000, 0b0000_0101, 1, 2, 3, 4, 5],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::Frame,
|
||||
has_extension: true,
|
||||
has_size_field: true,
|
||||
temporal_id: 4,
|
||||
spatial_id: 3,
|
||||
size: 5,
|
||||
leb_size: 1,
|
||||
header_len: 2,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0011_0110, 0b1001_1000, 0b0000_0101, 1, 2, 3, 4, 5],
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test_parse_rtp_obu() {
|
||||
for (idx, (sized_obu, raw_bytes)) in (*OBUS).iter().enumerate() {
|
||||
println!("running test {idx}...");
|
||||
|
||||
let mut reader = BitReader::endian(Cursor::new(&raw_bytes), BigEndian);
|
||||
|
||||
let obu_parsed = SizedObu::parse(&mut reader).unwrap();
|
||||
assert_eq!(&obu_parsed, sized_obu);
|
||||
|
||||
if let Some(seq_header_obu_bytes) = read_seq_header_obu_bytes(raw_bytes).unwrap() {
|
||||
println!("validation of sequence header obu read/write...");
|
||||
assert_eq!(&seq_header_obu_bytes, raw_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_seq_header_from_bitstream() {
|
||||
let mut bitstream = Vec::new();
|
||||
let mut seq_header_bytes_raw = None;
|
||||
for (obu, raw_bytes) in (*OBUS).iter() {
|
||||
bitstream.extend(raw_bytes);
|
||||
if obu.obu_type == ObuType::SequenceHeader {
|
||||
seq_header_bytes_raw = Some(raw_bytes.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let seq_header_obu_bytes = read_seq_header_obu_bytes(&bitstream).unwrap().unwrap();
|
||||
assert_eq!(seq_header_obu_bytes, seq_header_bytes_raw.unwrap());
|
||||
}
|
||||
}
|
|
@ -19,6 +19,33 @@ fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
fn to_completion(pipeline: &gst::Pipeline) {
|
||||
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");
|
||||
}
|
||||
|
||||
fn test_buffer_flags_single_stream(cmaf: bool, set_dts: bool, caps: gst::Caps) {
|
||||
let mut h = if cmaf {
|
||||
gst_check::Harness::new("cmafmux")
|
||||
|
@ -209,6 +236,26 @@ fn test_buffer_flags_single_vp9_stream_iso() {
|
|||
test_buffer_flags_single_stream(false, false, caps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_flags_single_av1_stream_cmaf() {
|
||||
init();
|
||||
|
||||
let caps = gst::Caps::builder("video/x-av1")
|
||||
.field("width", 1920i32)
|
||||
.field("height", 1080i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("profile", "main")
|
||||
.field("tier", "main")
|
||||
.field("level", "4.1")
|
||||
.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(true, false, caps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_flags_multi_stream() {
|
||||
init();
|
||||
|
@ -1287,6 +1334,328 @@ fn test_buffer_multi_stream_short_gops() {
|
|||
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_stream_manual_fragment() {
|
||||
init();
|
||||
|
||||
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();
|
||||
|
||||
let mut h = gst_check::Harness::new("cmafmux");
|
||||
|
||||
// fragment duration long enough to be ignored, 1s chunk duration
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.hours());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
// request fragment at 4 seconds, should be created at 11th buffer
|
||||
h.element()
|
||||
.unwrap()
|
||||
.emit_by_name::<()>("split-at-running-time", &[&4.seconds()]);
|
||||
|
||||
// Push 15 buffers of 0.5s each, 1st, 11th and 16th buffer without DELTA_UNIT flag
|
||||
for i in 0..20 {
|
||||
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(i * 500.mseconds());
|
||||
buffer.set_dts(i * 500.mseconds());
|
||||
buffer.set_duration(500.mseconds());
|
||||
if i != 0 && i != 10 && i != 15 {
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
}
|
||||
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
|
||||
|
||||
if i == 2 {
|
||||
let ev = loop {
|
||||
let ev = h.pull_upstream_event().unwrap();
|
||||
if ev.type_() != gst::EventType::Reconfigure
|
||||
&& ev.type_() != gst::EventType::Latency
|
||||
{
|
||||
break ev;
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(ev.type_(), gst::EventType::CustomUpstream);
|
||||
assert_eq!(
|
||||
gst_video::UpstreamForceKeyUnitEvent::parse(&ev).unwrap(),
|
||||
gst_video::UpstreamForceKeyUnitEvent {
|
||||
running_time: Some(4.seconds()),
|
||||
all_headers: true,
|
||||
count: 0
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
header.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
||||
);
|
||||
assert_eq!(header.pts(), Some(gst::ClockTime::ZERO));
|
||||
assert_eq!(header.dts(), Some(gst::ClockTime::ZERO));
|
||||
|
||||
// first fragment
|
||||
let fragment_header = h.pull().unwrap();
|
||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||
assert_eq!(fragment_header.pts(), Some(gst::ClockTime::ZERO));
|
||||
assert_eq!(fragment_header.dts(), Some(gst::ClockTime::ZERO));
|
||||
assert_eq!(fragment_header.duration(), Some(5.seconds()));
|
||||
|
||||
for buffer_idx in 0..10 {
|
||||
let buffer = h.pull().unwrap();
|
||||
if buffer_idx == 9 {
|
||||
assert_eq!(
|
||||
buffer.flags(),
|
||||
gst::BufferFlags::DELTA_UNIT | gst::BufferFlags::MARKER
|
||||
);
|
||||
} else {
|
||||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(buffer.pts(), Some(buffer_idx * 500.mseconds()));
|
||||
assert_eq!(buffer.dts(), Some(buffer_idx * 500.mseconds()));
|
||||
assert_eq!(buffer.duration(), Some(500.mseconds()));
|
||||
}
|
||||
|
||||
// second manual fragment
|
||||
let fragment_header = h.pull().unwrap();
|
||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||
assert_eq!(fragment_header.pts(), Some(5.seconds()));
|
||||
assert_eq!(fragment_header.dts(), Some(5.seconds()));
|
||||
assert_eq!(fragment_header.duration(), Some(2500.mseconds()));
|
||||
|
||||
for buffer_idx in 0..5 {
|
||||
let buffer = h.pull().unwrap();
|
||||
if buffer_idx == 4 {
|
||||
assert_eq!(
|
||||
buffer.flags(),
|
||||
gst::BufferFlags::DELTA_UNIT | gst::BufferFlags::MARKER
|
||||
);
|
||||
} else {
|
||||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(
|
||||
buffer.pts(),
|
||||
Some(5.seconds() + buffer_idx * 500.mseconds())
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.dts(),
|
||||
Some(5.seconds() + buffer_idx * 500.mseconds())
|
||||
);
|
||||
assert_eq!(buffer.duration(), Some(500.mseconds()));
|
||||
}
|
||||
|
||||
h.push_event(gst::event::Eos::new());
|
||||
|
||||
// There should be the second fragment now
|
||||
let fragment_header = h.pull().unwrap();
|
||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||
assert_eq!(fragment_header.pts(), Some(7500.mseconds()));
|
||||
assert_eq!(fragment_header.dts(), Some(7500.mseconds()));
|
||||
assert_eq!(fragment_header.duration(), Some(2500.mseconds()));
|
||||
|
||||
for buffer_idx in 0..5 {
|
||||
let buffer = h.pull().unwrap();
|
||||
if buffer_idx == 4 {
|
||||
assert_eq!(
|
||||
buffer.flags(),
|
||||
gst::BufferFlags::DELTA_UNIT | gst::BufferFlags::MARKER
|
||||
);
|
||||
} else {
|
||||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(
|
||||
buffer.pts(),
|
||||
Some(7500.mseconds() + buffer_idx * 500.mseconds())
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.dts(),
|
||||
Some(7500.mseconds() + buffer_idx * 500.mseconds())
|
||||
);
|
||||
assert_eq!(buffer.duration(), Some(500.mseconds()));
|
||||
}
|
||||
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::StreamStart);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Caps);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Segment);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunking_single_stream_manual_fragment() {
|
||||
init();
|
||||
|
||||
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();
|
||||
|
||||
let mut h = gst_check::Harness::new("cmafmux");
|
||||
|
||||
// fragment duration long enough to be ignored, 1s chunk duration
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.hours());
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("chunk-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
// request fragment at 4 seconds, should be created at 11th buffer
|
||||
h.element()
|
||||
.unwrap()
|
||||
.emit_by_name::<()>("split-at-running-time", &[&4.seconds()]);
|
||||
|
||||
// Push 15 buffers of 0.5s each, 1st and 11th buffer without DELTA_UNIT flag
|
||||
for i in 0..15 {
|
||||
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(i * 500.mseconds());
|
||||
buffer.set_dts(i * 500.mseconds());
|
||||
buffer.set_duration(500.mseconds());
|
||||
if i != 0 && i != 10 {
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
}
|
||||
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
|
||||
|
||||
if i == 2 {
|
||||
let ev = loop {
|
||||
let ev = h.pull_upstream_event().unwrap();
|
||||
if ev.type_() != gst::EventType::Reconfigure
|
||||
&& ev.type_() != gst::EventType::Latency
|
||||
{
|
||||
break ev;
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(ev.type_(), gst::EventType::CustomUpstream);
|
||||
assert_eq!(
|
||||
gst_video::UpstreamForceKeyUnitEvent::parse(&ev).unwrap(),
|
||||
gst_video::UpstreamForceKeyUnitEvent {
|
||||
running_time: Some(4.seconds()),
|
||||
all_headers: true,
|
||||
count: 0
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
header.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
||||
);
|
||||
assert_eq!(header.pts(), Some(gst::ClockTime::ZERO));
|
||||
assert_eq!(header.dts(), Some(gst::ClockTime::ZERO));
|
||||
|
||||
// There should be 7 chunks now, and the 1st and 6th are starting a fragment.
|
||||
// Each chunk should have two buffers.
|
||||
for chunk in 0..7 {
|
||||
let chunk_header = h.pull().unwrap();
|
||||
if chunk == 0 || chunk == 5 {
|
||||
assert_eq!(chunk_header.flags(), gst::BufferFlags::HEADER);
|
||||
} else {
|
||||
assert_eq!(
|
||||
chunk_header.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DELTA_UNIT
|
||||
);
|
||||
}
|
||||
assert_eq!(chunk_header.pts(), Some(chunk * 1.seconds()));
|
||||
assert_eq!(chunk_header.dts(), Some(chunk * 1.seconds()));
|
||||
assert_eq!(chunk_header.duration(), Some(1.seconds()));
|
||||
|
||||
for buffer_idx in 0..2 {
|
||||
let buffer = h.pull().unwrap();
|
||||
if buffer_idx == 1 {
|
||||
assert_eq!(
|
||||
buffer.flags(),
|
||||
gst::BufferFlags::DELTA_UNIT | gst::BufferFlags::MARKER
|
||||
);
|
||||
} else {
|
||||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(
|
||||
buffer.pts(),
|
||||
Some((chunk * 2 + buffer_idx) * 500.mseconds())
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.dts(),
|
||||
Some((chunk * 2 + buffer_idx) * 500.mseconds())
|
||||
);
|
||||
assert_eq!(buffer.duration(), Some(500.mseconds()));
|
||||
}
|
||||
}
|
||||
|
||||
h.push_event(gst::event::Eos::new());
|
||||
|
||||
// There should be the remaining chunk now, containing one 500ms buffer.
|
||||
for chunk in 7..8 {
|
||||
let chunk_header = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
chunk_header.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DELTA_UNIT
|
||||
);
|
||||
assert_eq!(chunk_header.pts(), Some(chunk * 1.seconds()));
|
||||
assert_eq!(chunk_header.dts(), Some(chunk * 1.seconds()));
|
||||
assert_eq!(chunk_header.duration(), Some(500.mseconds()));
|
||||
|
||||
for buffer_idx in 0..1 {
|
||||
let buffer = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
buffer.flags(),
|
||||
gst::BufferFlags::DELTA_UNIT | gst::BufferFlags::MARKER
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.pts(),
|
||||
Some((chunk * 2 + buffer_idx) * 500.mseconds())
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.dts(),
|
||||
Some((chunk * 2 + buffer_idx) * 500.mseconds())
|
||||
);
|
||||
assert_eq!(buffer.duration(), Some(500.mseconds()));
|
||||
}
|
||||
}
|
||||
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::StreamStart);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Caps);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Segment);
|
||||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunking_single_stream() {
|
||||
init();
|
||||
|
@ -1993,3 +2362,466 @@ fn test_chunking_single_stream_gops_after_fragment_end_after_next_chunk_end() {
|
|||
let ev = h.pull_event().unwrap();
|
||||
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_early_eos() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
for i in 0..5 {
|
||||
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(i * 100.mseconds());
|
||||
buffer.set_dts(i * 100.mseconds());
|
||||
buffer.set_duration(100.mseconds());
|
||||
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
|
||||
}
|
||||
|
||||
h.push_event(gst::event::Eos::new());
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_vp9_flac() {
|
||||
init();
|
||||
|
||||
let pipeline = gst::parse::launch(
|
||||
r#"
|
||||
videotestsrc num-buffers=99 ! vp9enc ! vp9parse ! mux.
|
||||
audiotestsrc num-buffers=149 ! flacenc ! flacparse ! mux.
|
||||
isofmp4mux name=mux ! qtdemux name=demux
|
||||
demux.audio_0 ! queue ! flacdec ! fakesink
|
||||
demux.video_0 ! queue ! vp9dec ! fakesink
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let pipeline = pipeline.downcast().unwrap();
|
||||
to_completion(&pipeline);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_caps_changed_verify(
|
||||
h: &mut gst_check::Harness,
|
||||
num_bufs: usize,
|
||||
caps_changed: bool,
|
||||
chunk: bool,
|
||||
) {
|
||||
for i in 0..num_bufs {
|
||||
let b = h.pull().unwrap();
|
||||
// FIXME: Rust 1.71 does not detect that the match is exhaustive so a `_` pattern has to be
|
||||
// added, but newer Rust warns (correctly) about that pattern being unreachable.
|
||||
#[allow(unreachable_patterns)]
|
||||
match (caps_changed, i, chunk) {
|
||||
(true, 0, _) => assert_eq!(
|
||||
b.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
||||
),
|
||||
(false, 0, false) | (true, 1, false) => assert_eq!(b.flags(), gst::BufferFlags::HEADER),
|
||||
(false, 0, true) | (true, 1, true) => assert_eq!(
|
||||
b.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DELTA_UNIT
|
||||
),
|
||||
(false, 1, _) | (_, 2.., _) => {
|
||||
if i == num_bufs - 1 {
|
||||
assert_eq!(
|
||||
b.flags(),
|
||||
gst::BufferFlags::MARKER | gst::BufferFlags::DELTA_UNIT
|
||||
);
|
||||
} else {
|
||||
assert_eq!(b.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn test_caps_changed_buffers(
|
||||
h: &mut gst_check::Harness,
|
||||
num_bufs: u64,
|
||||
gop_size: u64,
|
||||
caps_change: u64,
|
||||
duration: u64,
|
||||
key_frame_on_caps_change: bool,
|
||||
drop_first_buffer: bool,
|
||||
) {
|
||||
for i in 0..num_bufs {
|
||||
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(i * duration.mseconds());
|
||||
buffer.set_dts(i * duration.mseconds());
|
||||
buffer.set_duration(duration.mseconds());
|
||||
|
||||
if i % gop_size != 0 && (i != caps_change || !key_frame_on_caps_change) {
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 && drop_first_buffer {
|
||||
continue;
|
||||
}
|
||||
|
||||
if i == caps_change {
|
||||
let caps = gst::Caps::builder("video/x-h264")
|
||||
.field("width", 1280i32)
|
||||
.field("height", 720i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
h.push_event(gst::event::Caps::new(&caps));
|
||||
}
|
||||
assert_eq!(h.push(buffer), Ok(gst::FlowSuccess::Ok));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_at_gop_boundary() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Full GOP with HEADER and DISCONT due to caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Full GOP with HEADER but no DISCONT because no caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_at_gop_boundary_compatible() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
let caps = gst::Caps::builder("video/x-h264")
|
||||
.field("width", 1280i32)
|
||||
.field("height", 720i32)
|
||||
.field("framerate", gst::Fraction::new(10, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Full GOP with HEADER but no DISCONT because compatible caps
|
||||
// change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Full GOP with HEADER but no DISCONT because no caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_at_gop_boundary_not_allowed() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property_from_str("header-update-mode", "rewrite");
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 30, 10, 10, 100, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Full GOP with HEADER but no DISCONT because caps change not
|
||||
// allowed from header-update-modex
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Full GOP with HEADER but no DISCONT because no caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_within_gop() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 20, 10, 5, 100, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Reduced GOP with HEADER and DISCONT due to caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Full GOP with HEADER but no DISCONT because no caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_within_gop_start_without_key() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 20, 10, 5, 100, true, true);
|
||||
|
||||
// Same as test_caps_change_within_gop() but without the first
|
||||
// fragment since all frames are dropped due to missing key frame
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Reduced GOP with HEADER and DISCONT due to caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Full GOP with HEADER but no DISCONT because no caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_within_gop_chunked() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("chunk-duration", 300.mseconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 22, 10, 5, 30, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Fragment with HEADER and DISCONT due to caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Reduced chunk due to GOP end inbetween
|
||||
test_caps_changed_verify(&mut h, 1 + 5, false, true);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Everything left until EOS
|
||||
test_caps_changed_verify(&mut h, 1 + 2, false, true);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_within_gop_no_key() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 22, 10, 5, 100, false, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 5, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Reduced GOP with HEADER and DISCONT due to caps change
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
h.push_event(gst::event::Eos::new());
|
||||
// Everything left until EOS
|
||||
test_caps_changed_verify(&mut h, 1 + 2, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caps_change_before_first_frame() {
|
||||
init();
|
||||
|
||||
let mut h = gst_check::Harness::with_padnames("isofmp4mux", Some("sink_0"), Some("src"));
|
||||
|
||||
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::from_slice([1, 2, 3, 4]))
|
||||
.build();
|
||||
|
||||
h.element()
|
||||
.unwrap()
|
||||
.set_property("fragment-duration", 1.seconds());
|
||||
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
test_caps_changed_buffers(&mut h, 22, 10, 0, 100, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// Initial fragment with HEADER and DISCONT
|
||||
test_caps_changed_verify(&mut h, 1 + 1 + 10, true, false);
|
||||
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
// 2nd fragment with HEADER
|
||||
test_caps_changed_verify(&mut h, 1 + 10, false, false);
|
||||
|
||||
assert_eq!(h.buffers_in_queue(), 0);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ gst-audio = { workspace = true, features = ["v1_18"] }
|
|||
gst-video = { workspace = true, features = ["v1_18"] }
|
||||
gst-pbutils = { workspace = true, features = ["v1_18"] }
|
||||
once_cell.workspace = true
|
||||
bitstream-io = "2.3"
|
||||
|
||||
[lib]
|
||||
name = "gstmp4"
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
use gst::prelude::*;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Error};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::{ImageOrientation, IDENTITY_MATRIX};
|
||||
|
||||
fn write_box<T, F: FnOnce(&mut Vec<u8>) -> Result<T, Error>>(
|
||||
vec: &mut Vec<u8>,
|
||||
fourcc: impl std::borrow::Borrow<[u8; 4]>,
|
||||
|
@ -56,18 +58,31 @@ fn write_full_box<T, F: FnOnce(&mut Vec<u8>) -> Result<T, Error>>(
|
|||
}
|
||||
|
||||
/// Creates `ftyp` box
|
||||
pub(super) fn create_ftyp(variant: super::Variant) -> Result<gst::Buffer, Error> {
|
||||
pub(super) fn create_ftyp(
|
||||
variant: super::Variant,
|
||||
content_caps: &[&gst::CapsRef],
|
||||
) -> Result<gst::Buffer, Error> {
|
||||
let mut v = vec![];
|
||||
let mut minor_version = 0u32;
|
||||
|
||||
let (brand, compatible_brands) = match variant {
|
||||
let (brand, mut compatible_brands) = match variant {
|
||||
super::Variant::ISO | super::Variant::ONVIF => (b"iso4", vec![b"mp41", b"mp42", b"isom"]),
|
||||
};
|
||||
|
||||
for caps in content_caps {
|
||||
let s = caps.structure(0).unwrap();
|
||||
if let (super::Variant::ISO, "video/x-av1") = (variant, s.name().as_str()) {
|
||||
minor_version = 1;
|
||||
compatible_brands = vec![b"iso4", b"av01"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_box(&mut v, b"ftyp", |v| {
|
||||
// major brand
|
||||
v.extend(brand);
|
||||
// minor version
|
||||
v.extend(0u32.to_be_bytes());
|
||||
v.extend(minor_version.to_be_bytes());
|
||||
// compatible brands
|
||||
v.extend(compatible_brands.into_iter().flatten());
|
||||
|
||||
|
@ -382,9 +397,8 @@ fn write_tkhd(
|
|||
// Volume
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
match s.name().as_str() {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
v.extend((1u16 << 8).to_be_bytes())
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => v.extend((1u16 << 8).to_be_bytes()),
|
||||
_ => v.extend(0u16.to_be_bytes()),
|
||||
}
|
||||
|
||||
|
@ -392,21 +406,14 @@ fn write_tkhd(
|
|||
v.extend([0u8; 2]);
|
||||
|
||||
// Matrix
|
||||
v.extend(
|
||||
[
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(16384u32 << 16).to_be_bytes(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
let matrix = match s.name().as_str() {
|
||||
x if x.starts_with("video/") || x.starts_with("image/") => stream
|
||||
.orientation
|
||||
.unwrap_or(ImageOrientation::Rotate0)
|
||||
.transform_matrix(),
|
||||
_ => &IDENTITY_MATRIX,
|
||||
};
|
||||
v.extend(matrix.iter().flatten());
|
||||
|
||||
// Width/height
|
||||
match s.name().as_str() {
|
||||
|
@ -460,7 +467,6 @@ fn write_mdia(
|
|||
fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 {
|
||||
let lang = lang.borrow();
|
||||
|
||||
// TODO: Need to relax this once we get the language code from tags
|
||||
assert!(lang.iter().all(u8::is_ascii_lowercase));
|
||||
|
||||
(((lang[0] as u16 - 0x60) & 0x1F) << 10)
|
||||
|
@ -470,7 +476,7 @@ fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 {
|
|||
|
||||
fn write_mdhd(
|
||||
v: &mut Vec<u8>,
|
||||
_header: &super::Header,
|
||||
header: &super::Header,
|
||||
stream: &super::Stream,
|
||||
creation_time: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -493,8 +499,11 @@ fn write_mdhd(
|
|||
v.extend(duration.to_be_bytes());
|
||||
|
||||
// Language as ISO-639-2/T
|
||||
// TODO: get actual language from the tags
|
||||
if let Some(lang) = header.language_code {
|
||||
v.extend(language_code(lang).to_be_bytes());
|
||||
} else {
|
||||
v.extend(language_code(b"und").to_be_bytes());
|
||||
}
|
||||
|
||||
// Pre-defined
|
||||
v.extend([0u8; 2]);
|
||||
|
@ -514,9 +523,8 @@ fn write_hdlr(
|
|||
let (handler_type, name) = match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
|
||||
| "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()),
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
(b"soun", b"SoundHandler\0".as_slice())
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => (b"soun", b"SoundHandler\0".as_slice()),
|
||||
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
@ -546,7 +554,8 @@ fn write_minf(
|
|||
// Flags are always 1 for unspecified reasons
|
||||
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, header))?
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => {
|
||||
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_smhd(v, header)
|
||||
})?
|
||||
|
@ -703,9 +712,8 @@ fn write_stsd(
|
|||
match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp8" | "video/x-vp9" | "video/x-av1"
|
||||
| "image/jpeg" => write_visual_sample_entry(v, header, stream)?,
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
write_audio_sample_entry(v, header, stream)?
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-flac" | "audio/x-alaw" | "audio/x-mulaw"
|
||||
| "audio/x-adpcm" => write_audio_sample_entry(v, header, stream)?,
|
||||
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, header, stream)?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -916,8 +924,9 @@ fn write_visual_sample_entry(
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let level = 1; // FIXME
|
||||
let tier = 0; // FIXME
|
||||
// TODO: Use `gst_codec_utils_av1_get_seq_level_idx` when exposed in bindings
|
||||
let level = av1_seq_level_idx(s.get::<&str>("level").ok());
|
||||
let tier = av1_tier(s.get::<&str>("tier").ok());
|
||||
let (high_bitdepth, twelve_bit) =
|
||||
match s.get::<u32>("bit-depth-luma").unwrap() {
|
||||
8 => (false, false),
|
||||
|
@ -962,6 +971,10 @@ fn write_visual_sample_entry(
|
|||
v.extend_from_slice(&codec_data);
|
||||
}
|
||||
|
||||
if let Some(extra_data) = &stream.extra_header_data {
|
||||
// unsigned int(8) configOBUs[];
|
||||
v.extend_from_slice(extra_data.as_slice());
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
@ -1070,6 +1083,44 @@ fn write_visual_sample_entry(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn av1_seq_level_idx(level: Option<&str>) -> u8 {
|
||||
match level {
|
||||
Some("2.0") => 0,
|
||||
Some("2.1") => 1,
|
||||
Some("2.2") => 2,
|
||||
Some("2.3") => 3,
|
||||
Some("3.0") => 4,
|
||||
Some("3.1") => 5,
|
||||
Some("3.2") => 6,
|
||||
Some("3.3") => 7,
|
||||
Some("4.0") => 8,
|
||||
Some("4.1") => 9,
|
||||
Some("4.2") => 10,
|
||||
Some("4.3") => 11,
|
||||
Some("5.0") => 12,
|
||||
Some("5.1") => 13,
|
||||
Some("5.2") => 14,
|
||||
Some("5.3") => 15,
|
||||
Some("6.0") => 16,
|
||||
Some("6.1") => 17,
|
||||
Some("6.2") => 18,
|
||||
Some("6.3") => 19,
|
||||
Some("7.0") => 20,
|
||||
Some("7.1") => 21,
|
||||
Some("7.2") => 22,
|
||||
Some("7.3") => 23,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn av1_tier(tier: Option<&str>) -> u8 {
|
||||
match tier {
|
||||
Some("main") => 0,
|
||||
Some("high") => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_audio_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_header: &super::Header,
|
||||
|
@ -1079,6 +1130,7 @@ fn write_audio_sample_entry(
|
|||
let fourcc = match s.name().as_str() {
|
||||
"audio/mpeg" => b"mp4a",
|
||||
"audio/x-opus" => b"Opus",
|
||||
"audio/x-flac" => b"fLaC",
|
||||
"audio/x-alaw" => b"alaw",
|
||||
"audio/x-mulaw" => b"ulaw",
|
||||
"audio/x-adpcm" => {
|
||||
|
@ -1097,6 +1149,10 @@ fn write_audio_sample_entry(
|
|||
let bitrate = s.get::<i32>("bitrate").context("no ADPCM bitrate field")?;
|
||||
(bitrate / 8000) as u16
|
||||
}
|
||||
"audio/x-flac" => with_flac_metadata(&stream.caps, |streaminfo, _| {
|
||||
1 + (u16::from_be_bytes([streaminfo[16], streaminfo[17]]) >> 4 & 0b11111)
|
||||
})
|
||||
.context("FLAC metadata error")?,
|
||||
_ => 16u16,
|
||||
};
|
||||
|
||||
|
@ -1139,6 +1195,9 @@ fn write_audio_sample_entry(
|
|||
"audio/x-opus" => {
|
||||
write_dops(v, &stream.caps)?;
|
||||
}
|
||||
"audio/x-flac" => {
|
||||
write_dfla(v, &stream.caps)?;
|
||||
}
|
||||
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
@ -1333,6 +1392,35 @@ fn write_dops(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
|
|||
})
|
||||
}
|
||||
|
||||
fn with_flac_metadata<R>(
|
||||
caps: &gst::Caps,
|
||||
cb: impl FnOnce(&[u8], &[gst::glib::SendValue]) -> R,
|
||||
) -> Result<R, Error> {
|
||||
let caps = caps.structure(0).unwrap();
|
||||
let header = caps.get::<gst::ArrayRef>("streamheader").unwrap();
|
||||
let (streaminfo, remainder) = header.as_ref().split_first().unwrap();
|
||||
let streaminfo = streaminfo.get::<&gst::BufferRef>().unwrap();
|
||||
let streaminfo = streaminfo.map_readable().unwrap();
|
||||
// 13 bytes for the Ogg/FLAC prefix and 38 for the streaminfo itself.
|
||||
match <&[_; 13 + 38]>::try_from(streaminfo.as_slice()) {
|
||||
Ok(i) if i.starts_with(b"\x7FFLAC\x01\x00") => Ok(cb(&i[13..], remainder)),
|
||||
Ok(_) | Err(_) => bail!("Unknown streamheader format"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_dfla(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
|
||||
write_full_box(v, b"dfLa", 0, 0, move |v| {
|
||||
with_flac_metadata(caps, |streaminfo, remainder| {
|
||||
v.extend(streaminfo);
|
||||
for metadata in remainder {
|
||||
let metadata = metadata.get::<&gst::BufferRef>().unwrap();
|
||||
let metadata = metadata.map_readable().unwrap();
|
||||
v.extend(&metadata[..]);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn write_xml_meta_data_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_header: &super::Header,
|
||||
|
|
|
@ -15,9 +15,10 @@ use gst_base::subclass::prelude::*;
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::mp4mux::obu::read_seq_header_obu_bytes;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::boxes;
|
||||
use super::{boxes, ImageOrientation};
|
||||
|
||||
/// Offset between NTP and UNIX epoch in seconds.
|
||||
/// NTP = UNIX + NTP_UNIX_OFFSET.
|
||||
|
@ -108,6 +109,8 @@ struct Stream {
|
|||
caps: gst::Caps,
|
||||
/// Whether this stream is intra-only and has frame reordering.
|
||||
delta_frames: super::DeltaFrames,
|
||||
/// Whether this stream might have header frames without timestamps that should be ignored.
|
||||
discard_header_buffers: bool,
|
||||
|
||||
/// Already written out chunks with their samples for this stream
|
||||
chunks: Vec<super::Chunk>,
|
||||
|
@ -133,6 +136,11 @@ struct Stream {
|
|||
|
||||
/// In ONVIF mode, the mapping between running time and UTC time (UNIX)
|
||||
running_time_utc_time_mapping: Option<(gst::Signed<gst::ClockTime>, gst::ClockTime)>,
|
||||
|
||||
extra_header_data: Option<Vec<u8>>,
|
||||
|
||||
/// Orientation from tags
|
||||
orientation: Option<ImageOrientation>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -151,6 +159,9 @@ struct State {
|
|||
|
||||
/// Size of the `mdat` as written so far.
|
||||
mdat_size: u64,
|
||||
|
||||
/// Language code from tags
|
||||
language_code: Option<[u8; 3]>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -165,19 +176,24 @@ impl MP4Mux {
|
|||
buffer: &gst::BufferRef,
|
||||
sinkpad: &super::MP4MuxPad,
|
||||
delta_frames: super::DeltaFrames,
|
||||
discard_headers: bool,
|
||||
) -> Result<(), gst::FlowError> {
|
||||
if discard_headers && buffer.flags().contains(gst::BufferFlags::HEADER) {
|
||||
return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA);
|
||||
}
|
||||
|
||||
if delta_frames.requires_dts() && buffer.dts().is_none() {
|
||||
gst::error!(CAT, obj: sinkpad, "Require DTS for video streams");
|
||||
gst::error!(CAT, obj = sinkpad, "Require DTS for video streams");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
if buffer.pts().is_none() {
|
||||
gst::error!(CAT, obj: sinkpad, "Require timestamped buffers");
|
||||
gst::error!(CAT, obj = sinkpad, "Require timestamped buffers");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
if delta_frames.intra_only() && buffer.flags().contains(gst::BufferFlags::DELTA_UNIT) {
|
||||
gst::error!(CAT, obj: sinkpad, "Intra-only stream with delta units");
|
||||
gst::error!(CAT, obj = sinkpad, "Intra-only stream with delta units");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
|
@ -188,6 +204,7 @@ impl MP4Mux {
|
|||
&self,
|
||||
sinkpad: &super::MP4MuxPad,
|
||||
delta_frames: super::DeltaFrames,
|
||||
discard_headers: bool,
|
||||
pre_queue: &mut VecDeque<(gst::FormattedSegment<gst::ClockTime>, gst::Buffer)>,
|
||||
running_time_utc_time_mapping: &Option<(gst::Signed<gst::ClockTime>, gst::ClockTime)>,
|
||||
) -> Result<Option<(gst::FormattedSegment<gst::ClockTime>, gst::Buffer)>, gst::FlowError> {
|
||||
|
@ -195,17 +212,14 @@ impl MP4Mux {
|
|||
return Ok(Some((segment.clone(), buffer.clone())));
|
||||
}
|
||||
|
||||
let mut buffer = match sinkpad.peek_buffer() {
|
||||
None => return Ok(None),
|
||||
Some(buffer) => buffer,
|
||||
let Some(mut buffer) = sinkpad.peek_buffer() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Self::check_buffer(&buffer, sinkpad, delta_frames)?;
|
||||
|
||||
Self::check_buffer(&buffer, sinkpad, delta_frames, discard_headers)?;
|
||||
let mut segment = match sinkpad.segment().downcast::<gst::ClockTime>().ok() {
|
||||
Some(segment) => segment,
|
||||
None => {
|
||||
gst::error!(CAT, obj: sinkpad, "Got buffer before segment");
|
||||
gst::error!(CAT, obj = sinkpad, "Got buffer before segment");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
@ -231,7 +245,7 @@ impl MP4Mux {
|
|||
// Calculate from the mapping
|
||||
running_time_to_utc_time(pts, running_time_utc_time_mapping).ok_or_else(
|
||||
|| {
|
||||
gst::error!(CAT, obj: sinkpad, "Stream has negative PTS UTC time");
|
||||
gst::error!(CAT, obj = sinkpad, "Stream has negative PTS UTC time");
|
||||
gst::FlowError::Error
|
||||
},
|
||||
)?
|
||||
|
@ -241,7 +255,7 @@ impl MP4Mux {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Mapped PTS running time {pts} to UTC time {utc_time}"
|
||||
);
|
||||
|
||||
|
@ -252,12 +266,12 @@ impl MP4Mux {
|
|||
if let Some(dts) = dts {
|
||||
let dts_utc_time =
|
||||
running_time_to_utc_time(dts, (pts, utc_time)).ok_or_else(|| {
|
||||
gst::error!(CAT, obj: sinkpad, "Stream has negative DTS UTC time");
|
||||
gst::error!(CAT, obj = sinkpad, "Stream has negative DTS UTC time");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Mapped DTS running time {dts} to UTC time {dts_utc_time}"
|
||||
);
|
||||
buffer.set_dts(dts_utc_time);
|
||||
|
@ -276,19 +290,20 @@ impl MP4Mux {
|
|||
|
||||
fn pop_buffer(
|
||||
&self,
|
||||
sinkpad: &super::MP4MuxPad,
|
||||
delta_frames: super::DeltaFrames,
|
||||
pre_queue: &mut VecDeque<(gst::FormattedSegment<gst::ClockTime>, gst::Buffer)>,
|
||||
running_time_utc_time_mapping: &mut Option<(gst::Signed<gst::ClockTime>, gst::ClockTime)>,
|
||||
stream: &mut Stream,
|
||||
) -> Result<Option<(gst::FormattedSegment<gst::ClockTime>, gst::Buffer)>, gst::FlowError> {
|
||||
let Stream {
|
||||
sinkpad, pre_queue, ..
|
||||
} = stream;
|
||||
|
||||
// In ONVIF mode we need to get UTC times for each buffer and synchronize based on that.
|
||||
// Queue up to 6s of data to get the first UTC time and then backdate.
|
||||
if self.obj().class().as_ref().variant == super::Variant::ONVIF
|
||||
&& running_time_utc_time_mapping.is_none()
|
||||
&& stream.running_time_utc_time_mapping.is_none()
|
||||
{
|
||||
if let Some((last, first)) = Option::zip(pre_queue.back(), pre_queue.front()) {
|
||||
// Existence of PTS/DTS checked below
|
||||
let (last, first) = if delta_frames.requires_dts() {
|
||||
let (last, first) = if stream.delta_frames.requires_dts() {
|
||||
(
|
||||
last.0.to_running_time_full(last.1.dts()).unwrap(),
|
||||
first.0.to_running_time_full(first.1.dts()).unwrap(),
|
||||
|
@ -305,31 +320,32 @@ impl MP4Mux {
|
|||
{
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Got no UTC time in the first 6s of the stream"
|
||||
);
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
}
|
||||
|
||||
let buffer = match sinkpad.pop_buffer() {
|
||||
None => {
|
||||
let Some(buffer) = sinkpad.pop_buffer() else {
|
||||
if sinkpad.is_eos() {
|
||||
gst::error!(CAT, obj: sinkpad, "Got no UTC time before EOS");
|
||||
gst::error!(CAT, obj = sinkpad, "Got no UTC time before EOS");
|
||||
return Err(gst::FlowError::Error);
|
||||
} else {
|
||||
return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA);
|
||||
}
|
||||
}
|
||||
Some(buffer) => buffer,
|
||||
};
|
||||
|
||||
Self::check_buffer(&buffer, sinkpad, delta_frames)?;
|
||||
Self::check_buffer(
|
||||
&buffer,
|
||||
sinkpad,
|
||||
stream.delta_frames,
|
||||
stream.discard_header_buffers,
|
||||
)?;
|
||||
|
||||
let segment = match sinkpad.segment().downcast::<gst::ClockTime>().ok() {
|
||||
Some(segment) => segment,
|
||||
None => {
|
||||
gst::error!(CAT, obj: sinkpad, "Got buffer before segment");
|
||||
gst::error!(CAT, obj = sinkpad, "Got buffer before segment");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
@ -345,12 +361,12 @@ impl MP4Mux {
|
|||
let running_time = segment.to_running_time_full(buffer.pts().unwrap()).unwrap();
|
||||
gst::info!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Got initial UTC time {utc_time} at PTS running time {running_time}",
|
||||
);
|
||||
|
||||
let mapping = (running_time, utc_time);
|
||||
*running_time_utc_time_mapping = Some(mapping);
|
||||
stream.running_time_utc_time_mapping = Some(mapping);
|
||||
|
||||
// Push the buffer onto the pre-queue and re-timestamp it and all other buffers
|
||||
// based on the mapping above.
|
||||
|
@ -361,12 +377,12 @@ impl MP4Mux {
|
|||
|
||||
let pts = segment.to_running_time_full(buffer.pts().unwrap()).unwrap();
|
||||
let pts_utc_time = running_time_to_utc_time(pts, mapping).ok_or_else(|| {
|
||||
gst::error!(CAT, obj: sinkpad, "Stream has negative PTS UTC time");
|
||||
gst::error!(CAT, obj = sinkpad, "Stream has negative PTS UTC time");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Mapped PTS running time {pts} to UTC time {pts_utc_time}"
|
||||
);
|
||||
buffer.set_pts(pts_utc_time);
|
||||
|
@ -374,12 +390,12 @@ impl MP4Mux {
|
|||
if let Some(dts) = buffer.dts() {
|
||||
let dts = segment.to_running_time_full(dts).unwrap();
|
||||
let dts_utc_time = running_time_to_utc_time(dts, mapping).ok_or_else(|| {
|
||||
gst::error!(CAT, obj: sinkpad, "Stream has negative DTS UTC time");
|
||||
gst::error!(CAT, obj = sinkpad, "Stream has negative DTS UTC time");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: sinkpad,
|
||||
obj = sinkpad,
|
||||
"Mapped DTS running time {dts} to UTC time {dts_utc_time}"
|
||||
);
|
||||
buffer.set_dts(dts_utc_time);
|
||||
|
@ -391,7 +407,7 @@ impl MP4Mux {
|
|||
// Fall through below and pop the first buffer finally
|
||||
}
|
||||
|
||||
if let Some((segment, buffer)) = pre_queue.pop_front() {
|
||||
if let Some((segment, buffer)) = stream.pre_queue.pop_front() {
|
||||
return Ok(Some((segment, buffer)));
|
||||
}
|
||||
|
||||
|
@ -400,23 +416,26 @@ impl MP4Mux {
|
|||
// for calculating the duration to the previous buffer, and then put into the pre-queue
|
||||
// - or this is the very first buffer and we just put it into the queue overselves above
|
||||
if self.obj().class().as_ref().variant == super::Variant::ONVIF {
|
||||
if sinkpad.is_eos() {
|
||||
if stream.sinkpad.is_eos() {
|
||||
return Ok(None);
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
let buffer = match sinkpad.pop_buffer() {
|
||||
None => return Ok(None),
|
||||
Some(buffer) => buffer,
|
||||
let Some(buffer) = stream.sinkpad.pop_buffer() else {
|
||||
return Ok(None);
|
||||
};
|
||||
Self::check_buffer(
|
||||
&buffer,
|
||||
&stream.sinkpad,
|
||||
stream.delta_frames,
|
||||
stream.discard_header_buffers,
|
||||
)?;
|
||||
|
||||
Self::check_buffer(&buffer, sinkpad, delta_frames)?;
|
||||
|
||||
let segment = match sinkpad.segment().downcast::<gst::ClockTime>().ok() {
|
||||
let segment = match stream.sinkpad.segment().downcast::<gst::ClockTime>().ok() {
|
||||
Some(segment) => segment,
|
||||
None => {
|
||||
gst::error!(CAT, obj: sinkpad, "Got buffer before segment");
|
||||
gst::error!(CAT, obj = stream.sinkpad, "Got buffer before segment");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
@ -442,6 +461,12 @@ impl MP4Mux {
|
|||
Some(PendingBuffer {
|
||||
duration: Some(_), ..
|
||||
}) => return Ok(()),
|
||||
Some(PendingBuffer { ref buffer, .. })
|
||||
if stream.discard_header_buffers
|
||||
&& buffer.flags().contains(gst::BufferFlags::HEADER) =>
|
||||
{
|
||||
return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA);
|
||||
}
|
||||
Some(PendingBuffer {
|
||||
timestamp,
|
||||
pts,
|
||||
|
@ -449,26 +474,28 @@ impl MP4Mux {
|
|||
ref mut duration,
|
||||
..
|
||||
}) => {
|
||||
// Already have a pending buffer but no duration, so try to get that now
|
||||
let (segment, buffer) = match self.peek_buffer(
|
||||
let peek_outcome = self.peek_buffer(
|
||||
&stream.sinkpad,
|
||||
stream.delta_frames,
|
||||
stream.discard_header_buffers,
|
||||
&mut stream.pre_queue,
|
||||
&stream.running_time_utc_time_mapping,
|
||||
)? {
|
||||
)?;
|
||||
// Already have a pending buffer but no duration, so try to get that now
|
||||
let (segment, buffer) = match peek_outcome {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
if stream.sinkpad.is_eos() {
|
||||
let dur = buffer.duration().unwrap_or(gst::ClockTime::ZERO);
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Stream is EOS, using {dur} as duration for queued buffer",
|
||||
);
|
||||
|
||||
let pts = pts + dur;
|
||||
if stream.end_pts.map_or(true, |end_pts| end_pts < pts) {
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Stream end PTS {pts}");
|
||||
gst::trace!(CAT, obj = stream.sinkpad, "Stream end PTS {pts}");
|
||||
stream.end_pts = Some(pts);
|
||||
}
|
||||
|
||||
|
@ -476,7 +503,11 @@ impl MP4Mux {
|
|||
|
||||
return Ok(());
|
||||
} else {
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Stream has no buffer queued");
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Stream has no buffer queued"
|
||||
);
|
||||
return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA);
|
||||
}
|
||||
}
|
||||
|
@ -497,7 +528,7 @@ impl MP4Mux {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Stream has buffer with timestamp {next_timestamp} queued",
|
||||
);
|
||||
|
||||
|
@ -507,7 +538,7 @@ impl MP4Mux {
|
|||
.unwrap_or_else(|| {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Stream timestamps going backwards {next_timestamp} < {timestamp}",
|
||||
);
|
||||
gst::ClockTime::ZERO
|
||||
|
@ -515,41 +546,57 @@ impl MP4Mux {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Using {dur} as duration for queued buffer",
|
||||
);
|
||||
|
||||
let pts = pts + dur;
|
||||
if stream.end_pts.map_or(true, |end_pts| end_pts < pts) {
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Stream end PTS {pts}");
|
||||
gst::trace!(CAT, obj = stream.sinkpad, "Stream end PTS {pts}");
|
||||
stream.end_pts = Some(pts);
|
||||
}
|
||||
|
||||
*duration = Some(dur);
|
||||
|
||||
// If the stream is AV1, we need to parse the SequenceHeader OBU to include in the
|
||||
// extra data of the 'av1C' box. It makes the stream playable in some browsers.
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
if !buffer.flags().contains(gst::BufferFlags::DELTA_UNIT)
|
||||
&& s.name().as_str() == "video/x-av1"
|
||||
{
|
||||
let buf_map = buffer.map_readable().map_err(|_| {
|
||||
gst::error!(CAT, obj = stream.sinkpad, "Failed to map buffer");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
stream.extra_header_data = read_seq_header_obu_bytes(buf_map.as_slice())
|
||||
.map_err(|_| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Failed to parse AV1 SequenceHeader OBU"
|
||||
);
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
None => {
|
||||
// Have no buffer queued at all yet
|
||||
|
||||
let (segment, buffer) = match self.pop_buffer(
|
||||
&stream.sinkpad,
|
||||
stream.delta_frames,
|
||||
&mut stream.pre_queue,
|
||||
&mut stream.running_time_utc_time_mapping,
|
||||
)? {
|
||||
let (segment, buffer) = match self.pop_buffer(stream)? {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
if stream.sinkpad.is_eos() {
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
"Stream is EOS",
|
||||
);
|
||||
gst::trace!(CAT, obj = stream.sinkpad, "Stream is EOS",);
|
||||
|
||||
return Err(gst::FlowError::Eos);
|
||||
} else {
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Stream has no buffer queued");
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Stream has no buffer queued"
|
||||
);
|
||||
return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA);
|
||||
}
|
||||
}
|
||||
|
@ -559,9 +606,16 @@ impl MP4Mux {
|
|||
let pts_position = buffer.pts().unwrap();
|
||||
let dts_position = buffer.dts();
|
||||
|
||||
let pts = segment.to_running_time_full(pts_position).unwrap()
|
||||
.positive().unwrap_or_else(|| {
|
||||
gst::error!(CAT, obj: stream.sinkpad, "Stream has negative PTS running time");
|
||||
let pts = segment
|
||||
.to_running_time_full(pts_position)
|
||||
.unwrap()
|
||||
.positive()
|
||||
.unwrap_or_else(|| {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Stream has negative PTS running time"
|
||||
);
|
||||
gst::ClockTime::ZERO
|
||||
});
|
||||
|
||||
|
@ -573,7 +627,7 @@ impl MP4Mux {
|
|||
let dts = dts.unwrap();
|
||||
|
||||
if stream.start_dts.is_none() {
|
||||
gst::debug!(CAT, obj: stream.sinkpad, "Stream start DTS {dts}");
|
||||
gst::debug!(CAT, obj = stream.sinkpad, "Stream start DTS {dts}");
|
||||
stream.start_dts = Some(dts);
|
||||
}
|
||||
|
||||
|
@ -586,7 +640,7 @@ impl MP4Mux {
|
|||
.earliest_pts
|
||||
.map_or(true, |earliest_pts| earliest_pts > pts)
|
||||
{
|
||||
gst::debug!(CAT, obj: stream.sinkpad, "Stream earliest PTS {pts}");
|
||||
gst::debug!(CAT, obj = stream.sinkpad, "Stream earliest PTS {pts}");
|
||||
stream.earliest_pts = Some(pts);
|
||||
}
|
||||
|
||||
|
@ -595,7 +649,7 @@ impl MP4Mux {
|
|||
let dts = dts.unwrap(); // set above
|
||||
|
||||
Some(i64::try_from((pts - dts).nseconds()).map_err(|_| {
|
||||
gst::error!(CAT, obj: stream.sinkpad, "Too big PTS/DTS difference");
|
||||
gst::error!(CAT, obj = stream.sinkpad, "Too big PTS/DTS difference");
|
||||
gst::FlowError::Error
|
||||
})?)
|
||||
} else {
|
||||
|
@ -604,7 +658,7 @@ impl MP4Mux {
|
|||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Stream has buffer of size {} with timestamp {timestamp} pending",
|
||||
buffer.size(),
|
||||
);
|
||||
|
@ -651,7 +705,7 @@ impl MP4Mux {
|
|||
}))
|
||||
{
|
||||
gst::trace!(CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Continuing current chunk: single stream {single_stream}, or {} >= {} and {} >= {}",
|
||||
gst::format::Bytes::from_u64(stream.queued_chunk_bytes),
|
||||
settings.interleave_bytes.map(gst::format::Bytes::from_u64).display(),
|
||||
|
@ -661,16 +715,25 @@ impl MP4Mux {
|
|||
}
|
||||
|
||||
state.current_stream_idx = None;
|
||||
gst::debug!(CAT,
|
||||
obj: stream.sinkpad,
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Switching to next chunk: {} < {} and {} < {}",
|
||||
gst::format::Bytes::from_u64(stream.queued_chunk_bytes),
|
||||
settings.interleave_bytes.map(gst::format::Bytes::from_u64).display(),
|
||||
stream.queued_chunk_time, settings.interleave_time.display(),
|
||||
settings
|
||||
.interleave_bytes
|
||||
.map(gst::format::Bytes::from_u64)
|
||||
.display(),
|
||||
stream.queued_chunk_time,
|
||||
settings.interleave_time.display(),
|
||||
);
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
gst::debug!(CAT, obj: stream.sinkpad, "Stream is EOS, switching to next stream");
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Stream is EOS, switching to next stream"
|
||||
);
|
||||
state.current_stream_idx = None;
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -699,10 +762,7 @@ impl MP4Mux {
|
|||
|
||||
let timestamp = stream.pending_buffer.as_ref().unwrap().timestamp;
|
||||
|
||||
gst::trace!(CAT,
|
||||
obj: stream.sinkpad,
|
||||
"Stream at timestamp {timestamp}",
|
||||
);
|
||||
gst::trace!(CAT, obj = stream.sinkpad, "Stream at timestamp {timestamp}",);
|
||||
|
||||
all_eos = false;
|
||||
|
||||
|
@ -730,21 +790,21 @@ impl MP4Mux {
|
|||
}
|
||||
|
||||
if !all_have_data_or_eos {
|
||||
gst::trace!(CAT, imp: self, "Not all streams have a buffer or are EOS");
|
||||
gst::trace!(CAT, imp = self, "Not all streams have a buffer or are EOS");
|
||||
Err(gst_base::AGGREGATOR_FLOW_NEED_DATA)
|
||||
} else if all_eos {
|
||||
gst::info!(CAT, imp: self, "All streams are EOS");
|
||||
gst::info!(CAT, imp = self, "All streams are EOS");
|
||||
Err(gst::FlowError::Eos)
|
||||
} else if let Some((idx, stream, earliest_timestamp)) = earliest_stream {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Stream is earliest stream with timestamp {earliest_timestamp}",
|
||||
);
|
||||
|
||||
gst::debug!(
|
||||
CAT,
|
||||
obj: stream.sinkpad,
|
||||
obj = stream.sinkpad,
|
||||
"Starting new chunk at offset {}",
|
||||
state.current_offset,
|
||||
);
|
||||
|
@ -778,7 +838,7 @@ impl MP4Mux {
|
|||
&& buffer.buffer.flags().contains(gst::BufferFlags::DROPPABLE)
|
||||
&& buffer.buffer.size() == 0
|
||||
{
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Skipping gap buffer {buffer:?}");
|
||||
gst::trace!(CAT, obj = stream.sinkpad, "Skipping gap buffer {buffer:?}");
|
||||
|
||||
// If a new chunk was just started for the gap buffer, don't bother and get rid
|
||||
// of this chunk again for now and search for the next stream.
|
||||
|
@ -796,10 +856,19 @@ impl MP4Mux {
|
|||
if let Some(previous_sample) =
|
||||
stream.chunks.last_mut().and_then(|c| c.samples.last_mut())
|
||||
{
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Adding gap duration {} to previous sample", buffer.duration.unwrap());
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Adding gap duration {} to previous sample",
|
||||
buffer.duration.unwrap()
|
||||
);
|
||||
previous_sample.duration += buffer.duration.unwrap();
|
||||
} else {
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Resetting stream start time because it started with a gap");
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Resetting stream start time because it started with a gap"
|
||||
);
|
||||
// If there was no previous sample yet then the next sample needs to start
|
||||
// earlier or alternatively we change the start PTS. We do the latter here
|
||||
// as otherwise the first sample would be displayed too early.
|
||||
|
@ -811,7 +880,12 @@ impl MP4Mux {
|
|||
continue;
|
||||
}
|
||||
|
||||
gst::trace!(CAT, obj: stream.sinkpad, "Handling buffer {buffer:?} at offset {}", state.current_offset);
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = stream.sinkpad,
|
||||
"Handling buffer {buffer:?} at offset {}",
|
||||
state.current_offset
|
||||
);
|
||||
|
||||
let duration = buffer.duration.unwrap();
|
||||
let composition_time_offset = buffer.composition_time_offset;
|
||||
|
@ -849,7 +923,7 @@ impl MP4Mux {
|
|||
}
|
||||
|
||||
fn create_streams(&self, state: &mut State) -> Result<(), gst::FlowError> {
|
||||
gst::info!(CAT, imp: self, "Creating streams");
|
||||
gst::info!(CAT, imp = self, "Creating streams");
|
||||
|
||||
for pad in self
|
||||
.obj()
|
||||
|
@ -860,20 +934,21 @@ impl MP4Mux {
|
|||
let caps = match pad.current_caps() {
|
||||
Some(caps) => caps,
|
||||
None => {
|
||||
gst::warning!(CAT, obj: pad, "Skipping pad without caps");
|
||||
gst::warning!(CAT, obj = pad, "Skipping pad without caps");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
gst::info!(CAT, obj: pad, "Configuring caps {caps:?}");
|
||||
gst::info!(CAT, obj = pad, "Configuring caps {caps:?}");
|
||||
|
||||
let s = caps.structure(0).unwrap();
|
||||
|
||||
let mut delta_frames = super::DeltaFrames::IntraOnly;
|
||||
let mut discard_header_buffers = false;
|
||||
match s.name().as_str() {
|
||||
"video/x-h264" | "video/x-h265" => {
|
||||
if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) {
|
||||
gst::error!(CAT, obj: pad, "Received caps without codec_data");
|
||||
gst::error!(CAT, obj = pad, "Received caps without codec_data");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
delta_frames = super::DeltaFrames::Bidirectional;
|
||||
|
@ -883,7 +958,7 @@ impl MP4Mux {
|
|||
}
|
||||
"video/x-vp9" => {
|
||||
if !s.has_field_with_type("colorimetry", str::static_type()) {
|
||||
gst::error!(CAT, obj: pad, "Received caps without colorimetry");
|
||||
gst::error!(CAT, obj = pad, "Received caps without colorimetry");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
delta_frames = super::DeltaFrames::PredictiveOnly;
|
||||
|
@ -894,7 +969,7 @@ impl MP4Mux {
|
|||
"image/jpeg" => (),
|
||||
"audio/mpeg" => {
|
||||
if !s.has_field_with_type("codec_data", gst::Buffer::static_type()) {
|
||||
gst::error!(CAT, obj: pad, "Received caps without codec_data");
|
||||
gst::error!(CAT, obj = pad, "Received caps without codec_data");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
}
|
||||
|
@ -905,14 +980,26 @@ impl MP4Mux {
|
|||
.and_then(|a| a.first().and_then(|v| v.get::<gst::Buffer>().ok()))
|
||||
{
|
||||
if gst_pbutils::codec_utils_opus_parse_header(&header, None).is_err() {
|
||||
gst::error!(CAT, obj: pad, "Received invalid Opus header");
|
||||
gst::error!(CAT, obj = pad, "Received invalid Opus header");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
} else if gst_pbutils::codec_utils_opus_parse_caps(&caps, None).is_err() {
|
||||
gst::error!(CAT, obj: pad, "Received invalid Opus caps");
|
||||
gst::error!(CAT, obj = pad, "Received invalid Opus caps");
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
}
|
||||
}
|
||||
"audio/x-flac" => {
|
||||
discard_header_buffers = true;
|
||||
if let Err(e) = s.get::<gst::ArrayRef>("streamheader") {
|
||||
gst::error!(
|
||||
CAT,
|
||||
obj = pad,
|
||||
"Muxing FLAC into MP4 needs streamheader: {}",
|
||||
e
|
||||
);
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
};
|
||||
}
|
||||
"audio/x-alaw" | "audio/x-mulaw" => (),
|
||||
"audio/x-adpcm" => (),
|
||||
"application/x-onvif-metadata" => (),
|
||||
|
@ -924,6 +1011,7 @@ impl MP4Mux {
|
|||
pre_queue: VecDeque::new(),
|
||||
caps,
|
||||
delta_frames,
|
||||
discard_header_buffers,
|
||||
chunks: Vec::new(),
|
||||
pending_buffer: None,
|
||||
queued_chunk_time: gst::ClockTime::ZERO,
|
||||
|
@ -932,11 +1020,13 @@ impl MP4Mux {
|
|||
earliest_pts: None,
|
||||
end_pts: None,
|
||||
running_time_utc_time_mapping: None,
|
||||
extra_header_data: None,
|
||||
orientation: None,
|
||||
});
|
||||
}
|
||||
|
||||
if state.streams.is_empty() {
|
||||
gst::error!(CAT, imp: self, "No streams available");
|
||||
gst::error!(CAT, imp = self, "No streams available");
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
|
@ -1071,7 +1161,7 @@ impl ElementImpl for MP4Mux {
|
|||
if !state.streams.is_empty() {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Can't request new pads after stream was started"
|
||||
);
|
||||
return None;
|
||||
|
@ -1093,7 +1183,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::trace!(CAT, obj: aggregator_pad, "Handling query {query:?}");
|
||||
gst::trace!(CAT, obj = aggregator_pad, "Handling query {query:?}");
|
||||
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Caps(q) => {
|
||||
|
@ -1127,14 +1217,14 @@ impl AggregatorImpl for MP4Mux {
|
|||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
use gst::EventView;
|
||||
|
||||
gst::trace!(CAT, obj: aggregator_pad, "Handling event {event:?}");
|
||||
gst::trace!(CAT, obj = aggregator_pad, "Handling event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Segment(ev) => {
|
||||
if ev.segment().format() != gst::Format::Time {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
obj: aggregator_pad,
|
||||
obj = aggregator_pad,
|
||||
"Received non-TIME segment, replacing with default TIME segment"
|
||||
);
|
||||
let segment = gst::FormattedSegment::<gst::ClockTime>::new();
|
||||
|
@ -1144,6 +1234,57 @@ impl AggregatorImpl for MP4Mux {
|
|||
}
|
||||
self.parent_sink_event_pre_queue(aggregator_pad, event)
|
||||
}
|
||||
EventView::Tag(ev) => {
|
||||
if let Some(tag_value) = ev.tag().get::<gst::tags::LanguageCode>() {
|
||||
let lang = tag_value.get();
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp = self,
|
||||
"Received language code from tags: {:?}",
|
||||
lang
|
||||
);
|
||||
|
||||
// Language as ISO-639-2/T
|
||||
if lang.len() == 3 && lang.chars().all(|c| c.is_ascii_lowercase()) {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
let mut language_code: [u8; 3] = [0; 3];
|
||||
for (out, c) in Iterator::zip(language_code.iter_mut(), lang.chars()) {
|
||||
*out = c as u8;
|
||||
}
|
||||
state.language_code = Some(language_code);
|
||||
}
|
||||
} else if let Some(tag_value) = ev.tag().get::<gst::tags::ImageOrientation>() {
|
||||
let orientation = tag_value.get();
|
||||
gst::trace!(
|
||||
CAT,
|
||||
obj = aggregator_pad,
|
||||
"Received image orientation from tags: {:?}",
|
||||
orientation
|
||||
);
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
for stream in &mut state.streams {
|
||||
if &stream.sinkpad == aggregator_pad {
|
||||
stream.orientation = match orientation {
|
||||
"rotate-0" => Some(ImageOrientation::Rotate0),
|
||||
"rotate-90" => Some(ImageOrientation::Rotate90),
|
||||
"rotate-180" => Some(ImageOrientation::Rotate180),
|
||||
"rotate-270" => Some(ImageOrientation::Rotate270),
|
||||
// TODO:
|
||||
// "flip-rotate-0" => Some(ImageOrientation::FlipRotate0),
|
||||
// "flip-rotate-90" => Some(ImageOrientation::FlipRotate90),
|
||||
// "flip-rotate-180" => Some(ImageOrientation::FlipRotate180),
|
||||
// "flip-rotate-270" => Some(ImageOrientation::FlipRotate270),
|
||||
_ => None,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.parent_sink_event_pre_queue(aggregator_pad, event)
|
||||
}
|
||||
_ => self.parent_sink_event_pre_queue(aggregator_pad, event),
|
||||
}
|
||||
}
|
||||
|
@ -1151,7 +1292,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
fn sink_event(&self, aggregator_pad: &gst_base::AggregatorPad, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::trace!(CAT, obj: aggregator_pad, "Handling event {event:?}");
|
||||
gst::trace!(CAT, obj = aggregator_pad, "Handling event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Tag(_ev) => {
|
||||
|
@ -1166,7 +1307,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
fn src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryViewMut;
|
||||
|
||||
gst::trace!(CAT, imp: self, "Handling query {query:?}");
|
||||
gst::trace!(CAT, imp = self, "Handling query {query:?}");
|
||||
|
||||
match query.view_mut() {
|
||||
QueryViewMut::Seeking(q) => {
|
||||
|
@ -1181,7 +1322,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
fn src_event(&self, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst::trace!(CAT, imp: self, "Handling event {event:?}");
|
||||
gst::trace!(CAT, imp = self, "Handling event {event:?}");
|
||||
|
||||
match event.view() {
|
||||
EventView::Seek(_ev) => false,
|
||||
|
@ -1190,7 +1331,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
}
|
||||
|
||||
fn flush(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst::info!(CAT, imp: self, "Flushing");
|
||||
gst::info!(CAT, imp = self, "Flushing");
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
for stream in &mut state.streams {
|
||||
|
@ -1204,7 +1345,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::trace!(CAT, imp: self, "Stopping");
|
||||
gst::trace!(CAT, imp = self, "Stopping");
|
||||
|
||||
let _ = self.parent_stop();
|
||||
|
||||
|
@ -1214,7 +1355,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::trace!(CAT, imp: self, "Starting");
|
||||
gst::trace!(CAT, imp = self, "Starting");
|
||||
|
||||
self.parent_start()?;
|
||||
|
||||
|
@ -1255,7 +1396,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
}
|
||||
} else {
|
||||
// Can't query downstream, have to assume downstream is seekable
|
||||
gst::warning!(CAT, imp: self, "Can't query downstream for seekability");
|
||||
gst::warning!(CAT, imp = self, "Can't query downstream for seekability");
|
||||
}
|
||||
|
||||
state = self.state.lock().unwrap();
|
||||
|
@ -1270,15 +1411,23 @@ impl AggregatorImpl for MP4Mux {
|
|||
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Creating ftyp box at offset {}",
|
||||
state.current_offset
|
||||
);
|
||||
|
||||
// ... and then create the ftyp box plus mdat box header so we can start outputting
|
||||
// actual data
|
||||
let ftyp = boxes::create_ftyp(self.obj().class().as_ref().variant).map_err(|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to create ftyp box: {err}");
|
||||
let ftyp = boxes::create_ftyp(
|
||||
self.obj().class().as_ref().variant,
|
||||
&state
|
||||
.streams
|
||||
.iter()
|
||||
.map(|s| s.caps.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp = self, "Failed to create ftyp box: {err}");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
state.current_offset += ftyp.size() as u64;
|
||||
|
@ -1286,13 +1435,13 @@ impl AggregatorImpl for MP4Mux {
|
|||
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Creating mdat box header at offset {}",
|
||||
state.current_offset
|
||||
);
|
||||
state.mdat_offset = Some(state.current_offset);
|
||||
let mdat = boxes::create_mdat_header(None).map_err(|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to create mdat box header: {err}");
|
||||
gst::error!(CAT, imp = self, "Failed to create mdat box header: {err}");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
state.current_offset += mdat.size() as u64;
|
||||
|
@ -1313,7 +1462,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Creating moov box now, mdat ends at offset {} with size {}",
|
||||
state.current_offset,
|
||||
state.mdat_size
|
||||
|
@ -1336,6 +1485,8 @@ impl AggregatorImpl for MP4Mux {
|
|||
earliest_pts,
|
||||
end_pts,
|
||||
chunks: stream.chunks,
|
||||
extra_header_data: stream.extra_header_data.clone(),
|
||||
orientation: stream.orientation,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1343,9 +1494,10 @@ impl AggregatorImpl for MP4Mux {
|
|||
variant: self.obj().class().as_ref().variant,
|
||||
movie_timescale: settings.movie_timescale,
|
||||
streams,
|
||||
language_code: state.language_code,
|
||||
})
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to create moov box: {err}");
|
||||
gst::error!(CAT, imp = self, "Failed to create moov box: {err}");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
state.current_offset += moov.size() as u64;
|
||||
|
@ -1360,7 +1512,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
|
||||
if !buffers.is_empty() {
|
||||
if let Err(err) = self.obj().finish_buffer_list(buffers) {
|
||||
gst::error!(CAT, imp: self, "Failed pushing buffers: {err:?}");
|
||||
gst::error!(CAT, imp = self, "Failed pushing buffers: {err:?}");
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
@ -1371,7 +1523,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
if let Some(mdat_offset) = state.mdat_offset {
|
||||
gst::info!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Rewriting mdat box header at offset {mdat_offset} with size {} now",
|
||||
state.mdat_size,
|
||||
);
|
||||
|
@ -1379,7 +1531,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
segment.set_start(gst::format::Bytes::from_u64(mdat_offset));
|
||||
state.current_offset = mdat_offset;
|
||||
let mdat = boxes::create_mdat_header(Some(state.mdat_size)).map_err(|err| {
|
||||
gst::error!(CAT, imp: self, "Failed to create mdat box header: {err}");
|
||||
gst::error!(CAT, imp = self, "Failed to create mdat box header: {err}");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
drop(state);
|
||||
|
@ -1388,7 +1540,7 @@ impl AggregatorImpl for MP4Mux {
|
|||
if let Err(err) = self.obj().finish_buffer(mdat) {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
imp = self,
|
||||
"Failed pushing updated mdat box header buffer downstream: {err:?}",
|
||||
);
|
||||
}
|
||||
|
@ -1523,6 +1675,11 @@ impl ElementImpl for ISOMP4Mux {
|
|||
.field("channels", gst::IntRange::new(1i32, 8))
|
||||
.field("rate", gst::IntRange::new(1, i32::MAX))
|
||||
.build(),
|
||||
gst::Structure::builder("audio/x-flac")
|
||||
.field("framed", true)
|
||||
.field("channels", gst::IntRange::<i32>::new(1, 8))
|
||||
.field("rate", gst::IntRange::<i32>::new(1, 10 * u16::MAX as i32))
|
||||
.build(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<gst::Caps>(),
|
||||
|
@ -1709,7 +1866,7 @@ impl AggregatorPadImpl for MP4MuxPad {
|
|||
let mux = aggregator.downcast_ref::<super::MP4Mux>().unwrap();
|
||||
let mut mux_state = mux.imp().state.lock().unwrap();
|
||||
|
||||
gst::info!(CAT, imp: self, "Flushing");
|
||||
gst::info!(CAT, imp = self, "Flushing");
|
||||
|
||||
for stream in &mut mux_state.streams {
|
||||
if stream.sinkpad == *self.obj() {
|
||||
|
|
|
@ -11,6 +11,7 @@ use gst::prelude::*;
|
|||
|
||||
mod boxes;
|
||||
mod imp;
|
||||
mod obu;
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct MP4MuxPad(ObjectSubclass<imp::MP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||
|
@ -50,6 +51,80 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum ImageOrientation {
|
||||
Rotate0,
|
||||
Rotate90,
|
||||
Rotate180,
|
||||
Rotate270,
|
||||
// TODO:
|
||||
// FlipRotate0,
|
||||
// FlipRotate90,
|
||||
// FlipRotate180,
|
||||
// FlipRotate270,
|
||||
}
|
||||
|
||||
type TransformMatrix = [[u8; 4]; 9];
|
||||
|
||||
const IDENTITY_MATRIX: TransformMatrix = [
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_90_MATRIX: TransformMatrix = [
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_180_MATRIX: TransformMatrix = [
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
const ROTATE_270_MATRIX: TransformMatrix = [
|
||||
0u32.to_be_bytes(),
|
||||
(-1i32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 16).to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
0u32.to_be_bytes(),
|
||||
(1u32 << 30).to_be_bytes(),
|
||||
];
|
||||
|
||||
impl ImageOrientation {
|
||||
pub(crate) fn transform_matrix(&self) -> &'static TransformMatrix {
|
||||
match self {
|
||||
ImageOrientation::Rotate0 => &IDENTITY_MATRIX,
|
||||
ImageOrientation::Rotate90 => &ROTATE_90_MATRIX,
|
||||
ImageOrientation::Rotate180 => &ROTATE_180_MATRIX,
|
||||
ImageOrientation::Rotate270 => &ROTATE_270_MATRIX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum DeltaFrames {
|
||||
/// Only single completely decodable frames
|
||||
|
@ -126,6 +201,12 @@ pub(crate) struct Stream {
|
|||
|
||||
/// All the chunks stored for this stream
|
||||
chunks: Vec<Chunk>,
|
||||
|
||||
// More data to be included in the fragmented stream header
|
||||
extra_header_data: Option<Vec<u8>>,
|
||||
|
||||
/// Orientation from tags
|
||||
orientation: Option<ImageOrientation>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -135,6 +216,7 @@ pub(crate) struct Header {
|
|||
/// Pre-defined movie timescale if not 0.
|
||||
movie_timescale: u32,
|
||||
streams: Vec<Stream>,
|
||||
language_code: Option<[u8; 3]>,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
|
|
303
mux/mp4/src/mp4mux/obu.rs
Normal file
303
mux/mp4/src/mp4mux/obu.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
//
|
||||
// Copyright (C) 2022 Vivienne Watermeier <vwatermeier@igalia.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(non_camel_case_types)]
|
||||
|
||||
use bitstream_io::{BigEndian, BitRead, BitReader, Endianness};
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
pub fn parse_leb128<R, E>(reader: &mut BitReader<R, E>) -> io::Result<(u32, u32)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
E: Endianness,
|
||||
{
|
||||
let mut value = 0;
|
||||
let mut num_bytes = 0;
|
||||
|
||||
for i in 0..8 {
|
||||
let byte = reader.read::<u32>(8)?;
|
||||
value |= (byte & 0x7f) << (i * 7);
|
||||
num_bytes += 1;
|
||||
if byte & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reader.byte_align();
|
||||
Ok((value, num_bytes))
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct SizedObu {
|
||||
pub obu_type: ObuType,
|
||||
pub has_extension: bool,
|
||||
/// If the OBU header is followed by a leb128 size field.
|
||||
pub has_size_field: bool,
|
||||
pub temporal_id: u8,
|
||||
pub spatial_id: u8,
|
||||
/// size of the OBU payload in bytes.
|
||||
/// This may refer to different sizes in different contexts, not always
|
||||
/// to the entire OBU payload as it is in the AV1 bitstream.
|
||||
pub size: u32,
|
||||
/// the number of bytes the leb128 size field will take up
|
||||
/// when written with write_leb128().
|
||||
/// This does not imply `has_size_field`, and does not necessarily match with
|
||||
/// the length of the internal size field if present.
|
||||
pub leb_size: u32,
|
||||
pub header_len: u32,
|
||||
/// indicates that only part of this OBU has been processed so far
|
||||
pub is_fragment: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ObuType {
|
||||
Reserved,
|
||||
SequenceHeader,
|
||||
TemporalDelimiter,
|
||||
FrameHeader,
|
||||
TileGroup,
|
||||
Metadata,
|
||||
Frame,
|
||||
RedundantFrameHeader,
|
||||
TileList,
|
||||
Padding,
|
||||
}
|
||||
|
||||
impl Default for ObuType {
|
||||
fn default() -> Self {
|
||||
Self::Reserved
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedObu {
|
||||
/// Parse an OBU header and size field. If the OBU is not expected to contain
|
||||
/// a size field, but the size is known from external information,
|
||||
/// parse as an `UnsizedObu` and use `to_sized`.
|
||||
pub fn parse<R, E>(reader: &mut BitReader<R, E>) -> io::Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
E: Endianness,
|
||||
{
|
||||
// check the forbidden bit
|
||||
if reader.read_bit()? {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"forbidden bit in OBU header is set",
|
||||
));
|
||||
}
|
||||
|
||||
let obu_type = reader.read::<u8>(4)?.into();
|
||||
let has_extension = reader.read_bit()?;
|
||||
|
||||
// require a size field
|
||||
if !reader.read_bit()? {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"expected a size field",
|
||||
));
|
||||
}
|
||||
|
||||
// ignore the reserved bit
|
||||
let _ = reader.read_bit()?;
|
||||
|
||||
let (temporal_id, spatial_id) = if has_extension {
|
||||
(reader.read::<u8>(3)?, reader.read::<u8>(2)?)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
reader.byte_align();
|
||||
|
||||
let (size, leb_size) = parse_leb128(reader)?;
|
||||
|
||||
Ok(Self {
|
||||
obu_type,
|
||||
has_extension,
|
||||
has_size_field: true,
|
||||
temporal_id,
|
||||
spatial_id,
|
||||
size,
|
||||
leb_size,
|
||||
header_len: has_extension as u32 + 1,
|
||||
is_fragment: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// The amount of bytes this OBU will take up, including the space needed for
|
||||
/// its leb128 size field.
|
||||
pub fn full_size(&self) -> u32 {
|
||||
self.size + self.leb_size + self.header_len
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_seq_header_obu_bytes(data: &[u8]) -> io::Result<Option<Vec<u8>>> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
while cursor.position() < data.len() as u64 {
|
||||
let obu_start = cursor.position();
|
||||
|
||||
let Ok(obu) = SizedObu::parse(&mut BitReader::endian(&mut cursor, BigEndian)) else {
|
||||
break;
|
||||
};
|
||||
|
||||
// set reader to the beginning of the OBU
|
||||
cursor.seek(SeekFrom::Start(obu_start))?;
|
||||
|
||||
if obu.obu_type != ObuType::SequenceHeader {
|
||||
// Skip the full OBU
|
||||
cursor.seek(SeekFrom::Current(obu.full_size() as i64))?;
|
||||
continue;
|
||||
};
|
||||
|
||||
// read the full OBU
|
||||
let mut bytes = vec![0; obu.full_size() as usize];
|
||||
cursor.read_exact(&mut bytes)?;
|
||||
|
||||
return Ok(Some(bytes));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
impl From<u8> for ObuType {
|
||||
fn from(n: u8) -> Self {
|
||||
assert!(n < 16);
|
||||
|
||||
match n {
|
||||
1 => Self::SequenceHeader,
|
||||
2 => Self::TemporalDelimiter,
|
||||
3 => Self::FrameHeader,
|
||||
4 => Self::TileGroup,
|
||||
5 => Self::Metadata,
|
||||
6 => Self::Frame,
|
||||
7 => Self::RedundantFrameHeader,
|
||||
8 => Self::TileList,
|
||||
15 => Self::Padding,
|
||||
_ => Self::Reserved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ObuType> for u8 {
|
||||
fn from(ty: ObuType) -> Self {
|
||||
match ty {
|
||||
ObuType::Reserved => 0,
|
||||
ObuType::SequenceHeader => 1,
|
||||
ObuType::TemporalDelimiter => 2,
|
||||
ObuType::FrameHeader => 3,
|
||||
ObuType::TileGroup => 4,
|
||||
ObuType::Metadata => 5,
|
||||
ObuType::Frame => 6,
|
||||
ObuType::RedundantFrameHeader => 7,
|
||||
ObuType::TileList => 8,
|
||||
ObuType::Padding => 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bitstream_io::{BigEndian, BitReader};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
static OBUS: Lazy<Vec<(SizedObu, Vec<u8>)>> = Lazy::new(|| {
|
||||
vec![
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::TemporalDelimiter,
|
||||
has_extension: false,
|
||||
has_size_field: true,
|
||||
temporal_id: 0,
|
||||
spatial_id: 0,
|
||||
size: 0,
|
||||
leb_size: 1,
|
||||
header_len: 1,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0001_0010, 0b0000_0000],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::Padding,
|
||||
has_extension: false,
|
||||
has_size_field: true,
|
||||
temporal_id: 0,
|
||||
spatial_id: 0,
|
||||
size: 10,
|
||||
leb_size: 1,
|
||||
header_len: 1,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0111_1010, 0b0000_1010, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::SequenceHeader,
|
||||
has_extension: true,
|
||||
has_size_field: true,
|
||||
temporal_id: 4,
|
||||
spatial_id: 3,
|
||||
size: 5,
|
||||
leb_size: 1,
|
||||
header_len: 2,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0000_1110, 0b1001_1000, 0b0000_0101, 1, 2, 3, 4, 5],
|
||||
),
|
||||
(
|
||||
SizedObu {
|
||||
obu_type: ObuType::Frame,
|
||||
has_extension: true,
|
||||
has_size_field: true,
|
||||
temporal_id: 4,
|
||||
spatial_id: 3,
|
||||
size: 5,
|
||||
leb_size: 1,
|
||||
header_len: 2,
|
||||
is_fragment: false,
|
||||
},
|
||||
vec![0b0011_0110, 0b1001_1000, 0b0000_0101, 1, 2, 3, 4, 5],
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test_parse_rtp_obu() {
|
||||
for (idx, (sized_obu, raw_bytes)) in (*OBUS).iter().enumerate() {
|
||||
println!("running test {idx}...");
|
||||
|
||||
let mut reader = BitReader::endian(Cursor::new(&raw_bytes), BigEndian);
|
||||
|
||||
let obu_parsed = SizedObu::parse(&mut reader).unwrap();
|
||||
assert_eq!(&obu_parsed, sized_obu);
|
||||
|
||||
if let Some(seq_header_obu_bytes) = read_seq_header_obu_bytes(raw_bytes).unwrap() {
|
||||
println!("validation of sequence header obu read/write...");
|
||||
assert_eq!(&seq_header_obu_bytes, raw_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_seq_header_from_bitstream() {
|
||||
let mut bitstream = Vec::new();
|
||||
let mut seq_header_bytes_raw = None;
|
||||
for (obu, raw_bytes) in (*OBUS).iter() {
|
||||
bitstream.extend(raw_bytes);
|
||||
if obu.obu_type == ObuType::SequenceHeader {
|
||||
seq_header_bytes_raw = Some(raw_bytes.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let seq_header_obu_bytes = read_seq_header_obu_bytes(&bitstream).unwrap().unwrap();
|
||||
assert_eq!(seq_header_obu_bytes, seq_header_bytes_raw.unwrap());
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
//
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use gst::prelude::*;
|
||||
use gst_pbutils::prelude::*;
|
||||
|
||||
|
@ -20,10 +22,6 @@ fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
init();
|
||||
|
||||
struct Pipeline(gst::Pipeline);
|
||||
impl std::ops::Deref for Pipeline {
|
||||
type Target = gst::Pipeline;
|
||||
|
@ -38,28 +36,12 @@ fn test_basic() {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
impl Pipeline {
|
||||
fn into_completion(self) {
|
||||
self.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) {
|
||||
for msg in self.bus().unwrap().iter_timed(gst::ClockTime::NONE) {
|
||||
use gst::MessageView;
|
||||
|
||||
match msg.view() {
|
||||
|
@ -76,17 +58,42 @@ fn test_basic() {
|
|||
}
|
||||
}
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Null)
|
||||
self.set_state(gst::State::Null)
|
||||
.expect("Unable to set the pipeline to the `Null` state");
|
||||
}
|
||||
}
|
||||
|
||||
drop(pipeline);
|
||||
fn test_basic_with(video_enc: &str, audio_enc: &str, cb: impl FnOnce(&Path)) {
|
||||
let Ok(pipeline) = gst::parse::launch(&format!(
|
||||
"videotestsrc num-buffers=99 ! {video_enc} ! mux. \
|
||||
audiotestsrc num-buffers=140 ! {audio_enc} ! mux. \
|
||||
isomp4mux name=mux ! filesink name=sink"
|
||||
)) else {
|
||||
println!("could not build encoding pipeline");
|
||||
return;
|
||||
};
|
||||
let pipeline = Pipeline(pipeline.downcast::<gst::Pipeline>().unwrap());
|
||||
|
||||
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.into_completion();
|
||||
|
||||
cb(&location)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_x264_aac() {
|
||||
init();
|
||||
test_basic_with("x264enc", "fdkaacenc", |location| {
|
||||
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)
|
||||
url::Url::from_file_path(location)
|
||||
.expect("Failed to convert filename to URL")
|
||||
.as_str(),
|
||||
)
|
||||
|
@ -123,4 +130,45 @@ fn test_basic() {
|
|||
caps.can_intersect(&gst::Caps::builder("video/x-h264").any_features().build()),
|
||||
"Unexpected video caps {caps:?}"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_vp9_flac() {
|
||||
init();
|
||||
test_basic_with("vp9enc ! vp9parse", "flacenc ! flacparse", |location| {
|
||||
let Ok(pipeline) = gst::parse::launch(
|
||||
"filesrc name=src ! qtdemux name=demux \
|
||||
demux.audio_0 ! queue ! flacdec ! fakesink \
|
||||
demux.video_0 ! queue ! vp9dec ! fakesink",
|
||||
) else {
|
||||
panic!("could not build decoding pipeline")
|
||||
};
|
||||
let pipeline = Pipeline(pipeline.downcast::<gst::Pipeline>().unwrap());
|
||||
pipeline
|
||||
.by_name("src")
|
||||
.unwrap()
|
||||
.set_property("location", location.display().to_string());
|
||||
pipeline.into_completion();
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip_av1_aac() {
|
||||
init();
|
||||
test_basic_with("av1enc ! av1parse", "avenc_aac ! aacparse", |location| {
|
||||
let Ok(pipeline) = gst::parse::launch(
|
||||
"filesrc name=src ! qtdemux name=demux \
|
||||
demux.audio_0 ! queue ! avdec_aac ! fakesink \
|
||||
demux.video_0 ! queue ! av1dec ! fakesink",
|
||||
) else {
|
||||
panic!("could not build decoding pipeline")
|
||||
};
|
||||
let pipeline = Pipeline(pipeline.downcast::<gst::Pipeline>().unwrap());
|
||||
pipeline
|
||||
.by_name("src")
|
||||
.unwrap()
|
||||
.set_property("location", location.display().to_string());
|
||||
pipeline.into_completion();
|
||||
})
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue