mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-01-02 23:38:45 +00:00
rtp: Add PCMU/PCMA RTP payloader / depayloader elements
These come with new generic RTP payloader, RTP raw-ish audio payloader and RTP depayloader base classes. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1424>
This commit is contained in:
parent
e09f9e9540
commit
f563f8334b
18 changed files with 7930 additions and 41 deletions
89
Cargo.lock
generated
89
Cargo.lock
generated
|
@ -2011,7 +2011,7 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -2028,7 +2028,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -2040,7 +2040,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"futures-channel",
|
||||
|
@ -2061,7 +2061,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate",
|
||||
|
@ -2073,7 +2073,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
|
@ -2088,7 +2088,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#03c5a850037c582a0a5fca07f8e1cd1cf924c4fc"
|
||||
source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=master#90fc2033d4824e1c80a575d03b3c164e9901faf8"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
@ -2630,13 +2630,18 @@ dependencies = [
|
|||
name = "gst-plugin-rtp"
|
||||
version = "0.13.0-alpha.1"
|
||||
dependencies = [
|
||||
"atomic_refcell",
|
||||
"bitstream-io",
|
||||
"chrono",
|
||||
"gst-plugin-version-helper",
|
||||
"gstreamer",
|
||||
"gstreamer-app",
|
||||
"gstreamer-check",
|
||||
"gstreamer-rtp",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rtp-types",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2954,7 +2959,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-channel",
|
||||
|
@ -2980,7 +2985,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-app"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
@ -2994,7 +2999,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-app-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-base-sys",
|
||||
|
@ -3006,7 +3011,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-audio"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glib",
|
||||
|
@ -3021,7 +3026,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-audio-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3034,7 +3039,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-base"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"atomic_refcell",
|
||||
"cfg-if",
|
||||
|
@ -3047,7 +3052,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-base-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3059,7 +3064,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-check"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3069,7 +3074,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-check-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3081,7 +3086,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3095,7 +3100,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-egl"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3107,7 +3112,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-egl-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-gl-sys",
|
||||
|
@ -3118,7 +3123,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3132,7 +3137,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-wayland"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3144,7 +3149,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-wayland-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-gl-sys",
|
||||
|
@ -3155,7 +3160,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-x11"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3167,7 +3172,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-gl-x11-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-gl-sys",
|
||||
|
@ -3178,7 +3183,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-net"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
|
@ -3189,7 +3194,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-net-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
|
@ -3201,7 +3206,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-pbutils"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3215,7 +3220,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-pbutils-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3229,7 +3234,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-rtp"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3240,7 +3245,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-rtp-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-base-sys",
|
||||
|
@ -3252,7 +3257,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-sdp"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3262,7 +3267,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-sdp-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-sys",
|
||||
|
@ -3273,7 +3278,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3284,7 +3289,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-utils"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"gstreamer",
|
||||
"gstreamer-app",
|
||||
|
@ -3296,7 +3301,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-video"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-channel",
|
||||
|
@ -3313,7 +3318,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-video-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -3326,7 +3331,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-webrtc"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"gstreamer",
|
||||
|
@ -3338,7 +3343,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gstreamer-webrtc-sys"
|
||||
version = "0.23.0"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#b10f395c2cf0dc6d1af35068c0960ea5b839158d"
|
||||
source = "git+https://gitlab.freedesktop.org/gstreamer/gstreamer-rs?branch=main#28fe70f479fcb2f22971fc29a3b81e1cef097578"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gstreamer-sdp-sys",
|
||||
|
@ -5390,6 +5395,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtp-types"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01b38bb7fd9425628876786934ade84ec5cb63905804f073583e6554d33f9af"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtsp-types"
|
||||
version = "0.1.1"
|
||||
|
|
|
@ -6311,11 +6311,548 @@
|
|||
}
|
||||
},
|
||||
"rank": "none"
|
||||
},
|
||||
"rtppcmadepay2": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "Depayload A-law from RTP packets (RFC 3551)",
|
||||
"hierarchy": [
|
||||
"GstRtpPcmaDepay2",
|
||||
"GstRtpPcmauDepay2",
|
||||
"GstRtpBaseDepay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Depayloader/Network/RTP",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "application/x-rtp:\n media: audio\n payload: 8\n clock-rate: 8000\napplication/x-rtp:\n media: audio\n clock-rate: [ 1, 2147483647 ]\n encoding-name: PCMA\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "audio/x-alaw:\n channels: 1\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
},
|
||||
"rtppcmapay2": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "Payload A-law Audio into RTP packets (RFC 3551)",
|
||||
"hierarchy": [
|
||||
"GstRtpPcmaPay2",
|
||||
"GstRtpPcmauPay2",
|
||||
"GstRtpBaseAudioPay2",
|
||||
"GstRtpBasePay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Payloader/Network/RTP",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "audio/x-alaw:\n channels: 1\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "application/x-rtp:\n media: audio\n payload: 8\n clock-rate: 8000\napplication/x-rtp:\n media: audio\n payload: [ 96, 127 ]\n encoding-name: PCMA\n clock-rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
},
|
||||
"rtppcmudepay2": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "Depayload µ-law from RTP packets (RFC 3551)",
|
||||
"hierarchy": [
|
||||
"GstRtpPcmuDepay2",
|
||||
"GstRtpPcmauDepay2",
|
||||
"GstRtpBaseDepay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Depayloader/Network/RTP",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "application/x-rtp:\n media: audio\n payload: 0\n clock-rate: 8000\napplication/x-rtp:\n media: audio\n clock-rate: [ 1, 2147483647 ]\n encoding-name: PCMU\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "audio/x-mulaw:\n channels: 1\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
},
|
||||
"rtppcmupay2": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "Payload µ-law Audio into RTP packets (RFC 3551)",
|
||||
"hierarchy": [
|
||||
"GstRtpPcmuPay2",
|
||||
"GstRtpPcmauPay2",
|
||||
"GstRtpBaseAudioPay2",
|
||||
"GstRtpBasePay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Payloader/Network/RTP",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "audio/x-mulaw:\n channels: 1\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "application/x-rtp:\n media: audio\n payload: 0\n clock-rate: 8000\napplication/x-rtp:\n media: audio\n payload: [ 96, 127 ]\n encoding-name: PCMU\n clock-rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
}
|
||||
},
|
||||
"filename": "gstrsrtp",
|
||||
"license": "MPL-2.0",
|
||||
"other-types": {},
|
||||
"other-types": {
|
||||
"GstRtpBaseAudioPay2": {
|
||||
"hierarchy": [
|
||||
"GstRtpBaseAudioPay2",
|
||||
"GstRtpBasePay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"alignment-threshold": {
|
||||
"blurb": "Timestamp alignment threshold in nanoseconds",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "40000000",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"discont-wait": {
|
||||
"blurb": "Window of time in nanoseconds to wait before creating a discontinuity",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "1000000000",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"max-ptime": {
|
||||
"blurb": "Maximum duration of the packet data in ns (-1 = unlimited up to MTU)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "18446744073709551615",
|
||||
"max": "9223372036854775807",
|
||||
"min": "-1",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gint64",
|
||||
"writable": true
|
||||
},
|
||||
"min-ptime": {
|
||||
"blurb": "Minimum duration of the packet data in ns (can't go above MTU)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "9223372036854775807",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gint64",
|
||||
"writable": true
|
||||
},
|
||||
"ptime-multiple": {
|
||||
"blurb": "Force buffers to be multiples of this duration in ns (0 disables)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "9223372036854775807",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gint64",
|
||||
"writable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"GstRtpBaseDepay2": {
|
||||
"hierarchy": [
|
||||
"GstRtpBaseDepay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"auto-header-extensions": {
|
||||
"blurb": "Whether RTP header extensions should be automatically enabled, if an implementation is available",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"extensions": {
|
||||
"blurb": "List of enabled RTP header extensions",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstValueArray",
|
||||
"writable": false
|
||||
},
|
||||
"max-reorder": {
|
||||
"blurb": "Maximum seqnum reorder before assuming sender has restarted",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "100",
|
||||
"max": "32767",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"source-info": {
|
||||
"blurb": "Add RTP source information as buffer metadata",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"stats": {
|
||||
"blurb": "Various statistics",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "application/x-rtp-depayload-stats;",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstStructure",
|
||||
"writable": false
|
||||
}
|
||||
},
|
||||
"signals": {
|
||||
"add-extension": {
|
||||
"action": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "arg0",
|
||||
"type": "GstRTPHeaderExtension"
|
||||
}
|
||||
],
|
||||
"return-type": "void",
|
||||
"when": "last"
|
||||
},
|
||||
"clear-extensions": {
|
||||
"action": true,
|
||||
"args": [],
|
||||
"return-type": "void",
|
||||
"when": "last"
|
||||
},
|
||||
"request-extension": {
|
||||
"args": [
|
||||
{
|
||||
"name": "arg0",
|
||||
"type": "guint"
|
||||
},
|
||||
{
|
||||
"name": "arg1",
|
||||
"type": "gchararray"
|
||||
}
|
||||
],
|
||||
"return-type": "GstRTPHeaderExtension",
|
||||
"when": "last"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GstRtpBasePay2": {
|
||||
"hierarchy": [
|
||||
"GstRtpBasePay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"auto-header-extensions": {
|
||||
"blurb": "Whether RTP header extensions should be automatically enabled, if an implementation is available",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"extensions": {
|
||||
"blurb": "List of enabled RTP header extensions",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstValueArray",
|
||||
"writable": false
|
||||
},
|
||||
"mtu": {
|
||||
"blurb": "Maximum size of one RTP packet",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "1400",
|
||||
"max": "-1",
|
||||
"min": "28",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"onvif-no-rate-control": {
|
||||
"blurb": "Enable ONVIF Rate-Control=no timestamping mode",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"pt": {
|
||||
"blurb": "Payload type of the packets",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "8",
|
||||
"max": "127",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"scale-rtptime": {
|
||||
"blurb": "Whether the RTP timestamp should be scaled with the rate (speed)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "true",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"seqnum": {
|
||||
"blurb": "RTP sequence number of the last packet",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "65535",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": false
|
||||
},
|
||||
"seqnum-offset": {
|
||||
"blurb": "Offset that is added to all RTP sequence numbers (-1 == random)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "-1",
|
||||
"max": "65535",
|
||||
"min": "-1",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gint",
|
||||
"writable": true
|
||||
},
|
||||
"source-info": {
|
||||
"blurb": "Add RTP source information as buffer metadata",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"ssrc": {
|
||||
"blurb": "SSRC of the packets (-1 == random)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "18446744073709551615",
|
||||
"max": "4294967295",
|
||||
"min": "-1",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gint64",
|
||||
"writable": true
|
||||
},
|
||||
"stats": {
|
||||
"blurb": "Various statistics",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "application/x-rtp-payload-stats;",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstStructure",
|
||||
"writable": false
|
||||
},
|
||||
"timestamp": {
|
||||
"blurb": "RTP timestamp of the last packet",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "65535",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": false
|
||||
},
|
||||
"timestamp-offset": {
|
||||
"blurb": "Offset that is added to all RTP timestamps (-1 == random)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "18446744073709551615",
|
||||
"max": "4294967295",
|
||||
"min": "-1",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gint64",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"signals": {
|
||||
"add-extension": {
|
||||
"action": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "arg0",
|
||||
"type": "GstRTPHeaderExtension"
|
||||
}
|
||||
],
|
||||
"return-type": "void",
|
||||
"when": "last"
|
||||
},
|
||||
"clear-extensions": {
|
||||
"action": true,
|
||||
"args": [],
|
||||
"return-type": "void",
|
||||
"when": "last"
|
||||
},
|
||||
"request-extension": {
|
||||
"args": [
|
||||
{
|
||||
"name": "arg0",
|
||||
"type": "guint"
|
||||
},
|
||||
{
|
||||
"name": "arg1",
|
||||
"type": "gchararray"
|
||||
}
|
||||
],
|
||||
"return-type": "GstRTPHeaderExtension",
|
||||
"when": "last"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GstRtpPcmauDepay2": {
|
||||
"hierarchy": [
|
||||
"GstRtpPcmauDepay2",
|
||||
"GstRtpBaseDepay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object"
|
||||
},
|
||||
"GstRtpPcmauPay2": {
|
||||
"hierarchy": [
|
||||
"GstRtpPcmauPay2",
|
||||
"GstRtpBaseAudioPay2",
|
||||
"GstRtpBasePay2",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object"
|
||||
}
|
||||
},
|
||||
"package": "gst-plugin-rtp",
|
||||
"source": "gst-plugin-rtp",
|
||||
"tracers": {},
|
||||
|
|
|
@ -9,14 +9,19 @@ description = "GStreamer Rust RTP Plugin"
|
|||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitstream-io = "2.0"
|
||||
gst = { workspace = true, features = ["v1_20"] }
|
||||
gst-rtp = { workspace = true, features = ["v1_20"]}
|
||||
atomic_refcell = "0.1"
|
||||
bitstream-io = "2.1"
|
||||
chrono = { version = "0.4", default-features = false }
|
||||
gst = { workspace = true, features = ["v1_20"] }
|
||||
gst-rtp = { workspace = true, features = ["v1_20"] }
|
||||
once_cell.workspace = true
|
||||
rand = { version = "0.8", default-features = false, features = ["std", "std_rng" ] }
|
||||
rtp-types = { version = "0.1" }
|
||||
smallvec = { version = "1.11", features = ["union", "write", "const_generics", "const_new"] }
|
||||
|
||||
[dev-dependencies]
|
||||
gst-check = { workspace = true, features = ["v1_20"] }
|
||||
gst-app = { workspace = true, features = ["v1_20"] }
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper.workspace = true
|
||||
|
|
193
net/rtp/src/audio_discont.rs
Normal file
193
net/rtp/src/audio_discont.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::{
|
||||
glib::{self, prelude::*},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AudioDiscont {
|
||||
/// If last processing detected a discontinuity.
|
||||
discont_pending: bool,
|
||||
/// Base PTS to which the offsets below are relative.
|
||||
base_pts: Option<gst::ClockTime>,
|
||||
/// Next output sample offset, i.e. offset of the first sample of the queued buffers.
|
||||
///
|
||||
/// This is only set once the packet with the base PTS is output.
|
||||
next_out_offset: Option<u64>,
|
||||
/// Next expected input sample offset.
|
||||
next_in_offset: Option<u64>,
|
||||
/// PTS of the last buffer that was above the alignment threshold.
|
||||
///
|
||||
/// This is reset whenever the next buffer is actually below the alignment threshold again.
|
||||
/// FIXME: Should this be running time?
|
||||
discont_time: Option<gst::ClockTime>,
|
||||
/// Last known sample rate.
|
||||
last_rate: Option<u32>,
|
||||
}
|
||||
|
||||
impl AudioDiscont {
|
||||
pub fn process_input(
|
||||
&mut self,
|
||||
settings: &AudioDiscontConfiguration,
|
||||
discont: bool,
|
||||
rate: u32,
|
||||
pts: gst::ClockTime,
|
||||
num_samples: usize,
|
||||
) -> bool {
|
||||
if self.discont_pending {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.last_rate.map_or(false, |last_rate| last_rate != rate) {
|
||||
self.discont_pending = true;
|
||||
}
|
||||
self.last_rate = Some(rate);
|
||||
|
||||
if discont {
|
||||
self.discont_pending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we have no base PTS yet, this is the first buffer and there's a discont
|
||||
let Some(base_pts) = self.base_pts else {
|
||||
self.discont_pending = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Never detect a discont if alignment threshold is not set
|
||||
let Some(alignment_threshold) = settings.alignment_threshold else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let expected_pts = base_pts
|
||||
+ gst::ClockTime::from_nseconds(
|
||||
self.next_in_offset
|
||||
.unwrap_or(0)
|
||||
.mul_div_ceil(gst::ClockTime::SECOND.nseconds(), rate as u64)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut discont = false;
|
||||
|
||||
let diff = pts.into_positive() - expected_pts.into_positive();
|
||||
if diff.abs() >= alignment_threshold {
|
||||
let mut resync = false;
|
||||
if settings.discont_wait.is_zero() {
|
||||
resync = true;
|
||||
} else if let Some(discont_time) = self.discont_time {
|
||||
if (discont_time.into_positive() - pts.into_positive()).abs()
|
||||
>= settings.discont_wait.into_positive()
|
||||
{
|
||||
resync = true;
|
||||
}
|
||||
} else if (expected_pts.into_positive() - pts.into_positive()).abs()
|
||||
>= settings.discont_wait.into_positive()
|
||||
{
|
||||
resync = true;
|
||||
} else {
|
||||
self.discont_time = Some(expected_pts);
|
||||
}
|
||||
|
||||
if resync {
|
||||
discont = true;
|
||||
}
|
||||
} else {
|
||||
self.discont_time = None;
|
||||
}
|
||||
|
||||
self.next_in_offset = Some(self.next_in_offset.unwrap_or(0) + num_samples as u64);
|
||||
|
||||
if discont {
|
||||
self.discont_pending = true;
|
||||
}
|
||||
|
||||
discont
|
||||
}
|
||||
|
||||
pub fn resync(&mut self, base_pts: gst::ClockTime, num_samples: usize) {
|
||||
self.discont_pending = false;
|
||||
self.base_pts = Some(base_pts);
|
||||
self.next_in_offset = Some(num_samples as u64);
|
||||
self.next_out_offset = None;
|
||||
self.discont_time = None;
|
||||
self.last_rate = None;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = AudioDiscont::default();
|
||||
}
|
||||
|
||||
pub fn base_pts(&self) -> Option<gst::ClockTime> {
|
||||
self.base_pts
|
||||
}
|
||||
|
||||
pub fn next_output_offset(&self) -> Option<u64> {
|
||||
self.next_out_offset
|
||||
}
|
||||
|
||||
pub fn process_output(&mut self, num_samples: usize) {
|
||||
self.next_out_offset = Some(self.next_out_offset.unwrap_or(0) + num_samples as u64);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AudioDiscontConfiguration {
|
||||
pub alignment_threshold: Option<gst::ClockTime>,
|
||||
pub discont_wait: gst::ClockTime,
|
||||
}
|
||||
|
||||
impl Default for AudioDiscontConfiguration {
|
||||
fn default() -> Self {
|
||||
AudioDiscontConfiguration {
|
||||
alignment_threshold: Some(gst::ClockTime::from_mseconds(40)),
|
||||
discont_wait: gst::ClockTime::from_seconds(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioDiscontConfiguration {
|
||||
pub fn create_pspecs() -> Vec<glib::ParamSpec> {
|
||||
vec![
|
||||
glib::ParamSpecUInt64::builder("alignment-threshold")
|
||||
.nick("Alignment Threshold")
|
||||
.blurb("Timestamp alignment threshold in nanoseconds")
|
||||
.default_value(
|
||||
Self::default()
|
||||
.alignment_threshold
|
||||
.map(gst::ClockTime::nseconds)
|
||||
.unwrap_or(u64::MAX),
|
||||
)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
glib::ParamSpecUInt64::builder("discont-wait")
|
||||
.nick("Discont Wait")
|
||||
.blurb("Window of time in nanoseconds to wait before creating a discontinuity")
|
||||
.default_value(Self::default().discont_wait.nseconds())
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn set_property(&mut self, value: &glib::Value, pspec: &glib::ParamSpec) -> bool {
|
||||
match pspec.name() {
|
||||
"alignment-threshold" => {
|
||||
self.alignment_threshold = value.get().unwrap();
|
||||
true
|
||||
}
|
||||
"discont-wait" => {
|
||||
self.discont_wait = value.get().unwrap();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn property(&self, pspec: &glib::ParamSpec) -> Option<glib::Value> {
|
||||
match pspec.name() {
|
||||
"alignment-threshold" => Some(self.alignment_threshold.to_value()),
|
||||
"discont-wait" => Some(self.discont_wait.to_value()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
518
net/rtp/src/baseaudiopay/imp.rs
Normal file
518
net/rtp/src/baseaudiopay/imp.rs
Normal file
|
@ -0,0 +1,518 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{cmp, collections::VecDeque, sync::Mutex};
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
audio_discont::{AudioDiscont, AudioDiscontConfiguration},
|
||||
basepay::{PacketToBufferRelation, RtpBasePay2Ext, RtpBasePay2ImplExt, TimestampOffset},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Settings {
|
||||
max_ptime: Option<gst::ClockTime>,
|
||||
min_ptime: gst::ClockTime,
|
||||
ptime_multiple: gst::ClockTime,
|
||||
audio_discont: AudioDiscontConfiguration,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
max_ptime: None,
|
||||
min_ptime: gst::ClockTime::ZERO,
|
||||
ptime_multiple: gst::ClockTime::ZERO,
|
||||
audio_discont: AudioDiscontConfiguration::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct QueuedBuffer {
|
||||
/// ID of the buffer.
|
||||
id: u64,
|
||||
/// The mapped buffer itself.
|
||||
buffer: gst::MappedBuffer<gst::buffer::Readable>,
|
||||
/// Offset into the buffer that was not consumed yet.
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
/// Currently configured clock rate.
|
||||
clock_rate: Option<u32>,
|
||||
/// Number of bytes per frame.
|
||||
bpf: Option<usize>,
|
||||
|
||||
/// Desired "packet time", i.e. packet duration, from the caps, if set.
|
||||
ptime: Option<gst::ClockTime>,
|
||||
max_ptime: Option<gst::ClockTime>,
|
||||
|
||||
/// Currently queued buffers.
|
||||
queued_buffers: VecDeque<QueuedBuffer>,
|
||||
/// Currently queued number of bytes.
|
||||
queued_bytes: usize,
|
||||
|
||||
audio_discont: AudioDiscont,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpBaseAudioPay2 {
|
||||
settings: Mutex<Settings>,
|
||||
state: AtomicRefCell<State>,
|
||||
}
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"rtpbaseaudiopay2",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Base RTP Audio Payloader"),
|
||||
)
|
||||
});
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpBaseAudioPay2 {
|
||||
const ABSTRACT: bool = true;
|
||||
const NAME: &'static str = "GstRtpBaseAudioPay2";
|
||||
type Type = super::RtpBaseAudioPay2;
|
||||
type ParentType = crate::basepay::RtpBasePay2;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpBaseAudioPay2 {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
let mut properties = vec![
|
||||
// Using same type/semantics as C payloaders
|
||||
glib::ParamSpecInt64::builder("max-ptime")
|
||||
.nick("Maximum Packet Time")
|
||||
.blurb("Maximum duration of the packet data in ns (-1 = unlimited up to MTU)")
|
||||
.default_value(
|
||||
Settings::default()
|
||||
.max_ptime
|
||||
.map(gst::ClockTime::nseconds)
|
||||
.map(|x| x as i64)
|
||||
.unwrap_or(-1),
|
||||
)
|
||||
.minimum(-1)
|
||||
.maximum(i64::MAX)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
// Using same type/semantics as C payloaders
|
||||
glib::ParamSpecInt64::builder("min-ptime")
|
||||
.nick("Minimum Packet Time")
|
||||
.blurb("Minimum duration of the packet data in ns (can't go above MTU)")
|
||||
.default_value(Settings::default().min_ptime.nseconds() as i64)
|
||||
.minimum(0)
|
||||
.maximum(i64::MAX)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
// Using same type/semantics as C payloaders
|
||||
glib::ParamSpecInt64::builder("ptime-multiple")
|
||||
.nick("Packet Time Multiple")
|
||||
.blurb("Force buffers to be multiples of this duration in ns (0 disables)")
|
||||
.default_value(Settings::default().ptime_multiple.nseconds() as i64)
|
||||
.minimum(0)
|
||||
.maximum(i64::MAX)
|
||||
.mutable_playing()
|
||||
.build(),
|
||||
];
|
||||
|
||||
properties.extend_from_slice(&AudioDiscontConfiguration::create_pspecs());
|
||||
|
||||
properties
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
if self
|
||||
.settings
|
||||
.lock()
|
||||
.unwrap()
|
||||
.audio_discont
|
||||
.set_property(value, pspec)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
match pspec.name() {
|
||||
"max-ptime" => {
|
||||
let v = value.get::<i64>().unwrap();
|
||||
self.settings.lock().unwrap().max_ptime =
|
||||
(v != -1).then_some(gst::ClockTime::from_nseconds(v as u64));
|
||||
}
|
||||
"min-ptime" => {
|
||||
let v = gst::ClockTime::from_nseconds(value.get::<i64>().unwrap() as u64);
|
||||
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
let changed = settings.min_ptime != v;
|
||||
settings.min_ptime = v;
|
||||
drop(settings);
|
||||
|
||||
if changed {
|
||||
let _ = self
|
||||
.obj()
|
||||
.post_message(gst::message::Latency::builder().src(&*self.obj()).build());
|
||||
}
|
||||
}
|
||||
"ptime-multiple" => {
|
||||
self.settings.lock().unwrap().ptime_multiple =
|
||||
gst::ClockTime::from_nseconds(value.get::<i64>().unwrap() as u64);
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
if let Some(value) = self.settings.lock().unwrap().audio_discont.property(pspec) {
|
||||
return value;
|
||||
}
|
||||
|
||||
match pspec.name() {
|
||||
"max-ptime" => (self
|
||||
.settings
|
||||
.lock()
|
||||
.unwrap()
|
||||
.max_ptime
|
||||
.map(gst::ClockTime::nseconds)
|
||||
.map(|x| x as i64)
|
||||
.unwrap_or(-1))
|
||||
.to_value(),
|
||||
"min-ptime" => (self.settings.lock().unwrap().min_ptime.nseconds() as i64).to_value(),
|
||||
"ptime-multiple" => {
|
||||
(self.settings.lock().unwrap().ptime_multiple.nseconds() as i64).to_value()
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for RtpBaseAudioPay2 {}
|
||||
|
||||
impl ElementImpl for RtpBaseAudioPay2 {}
|
||||
|
||||
impl crate::basepay::RtpBasePay2Impl for RtpBaseAudioPay2 {
|
||||
const ALLOWED_META_TAGS: &'static [&'static str] = &["audio"];
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
*self.state.borrow_mut() = State::default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
*self.state.borrow_mut() = State::default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn negotiate(&self, mut src_caps: gst::Caps) {
|
||||
// Fixate here as a first step
|
||||
src_caps.fixate();
|
||||
|
||||
let s = src_caps.structure(0).unwrap();
|
||||
|
||||
// Negotiate ptime/maxptime with downstream and use them in combination with the
|
||||
// properties. See https://datatracker.ietf.org/doc/html/rfc4566#section-6
|
||||
let ptime = s
|
||||
.get::<u32>("ptime")
|
||||
.ok()
|
||||
.map(u64::from)
|
||||
.map(gst::ClockTime::from_mseconds);
|
||||
|
||||
let max_ptime = s
|
||||
.get::<u32>("maxptime")
|
||||
.ok()
|
||||
.map(u64::from)
|
||||
.map(gst::ClockTime::from_mseconds);
|
||||
|
||||
let clock_rate = match s.get::<i32>("clock-rate") {
|
||||
Ok(clock_rate) if clock_rate > 0 => clock_rate as u32,
|
||||
_ => {
|
||||
panic!("RTP caps {src_caps:?} without 'clock-rate'");
|
||||
}
|
||||
};
|
||||
|
||||
self.parent_negotiate(src_caps);
|
||||
|
||||
// Draining happened above if the clock rate has changed
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.ptime = ptime;
|
||||
state.max_ptime = max_ptime;
|
||||
state.clock_rate = Some(clock_rate);
|
||||
drop(state);
|
||||
}
|
||||
|
||||
fn drain(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
let mut state = self.state.borrow_mut();
|
||||
self.drain_packets(&settings, &mut state, true)
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.queued_buffers.clear();
|
||||
state.queued_bytes = 0;
|
||||
state.audio_discont.reset();
|
||||
}
|
||||
|
||||
fn handle_buffer(
|
||||
&self,
|
||||
buffer: &gst::Buffer,
|
||||
id: u64,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let buffer = buffer.clone().into_mapped_buffer_readable().map_err(|_| {
|
||||
gst::error!(CAT, imp: self, "Can't map buffer readable");
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
let pts = buffer.buffer().pts().unwrap();
|
||||
|
||||
let settings = self.settings.lock().unwrap().clone();
|
||||
let mut state = self.state.borrow_mut();
|
||||
|
||||
let Some(bpf) = state.bpf else {
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
};
|
||||
let Some(clock_rate) = state.clock_rate else {
|
||||
return Err(gst::FlowError::NotNegotiated);
|
||||
};
|
||||
let num_samples = buffer.size() / bpf;
|
||||
|
||||
let discont = state.audio_discont.process_input(
|
||||
&settings.audio_discont,
|
||||
buffer.buffer().flags().contains(gst::BufferFlags::DISCONT),
|
||||
clock_rate,
|
||||
pts,
|
||||
num_samples,
|
||||
);
|
||||
|
||||
if discont {
|
||||
if state.audio_discont.base_pts().is_some() {
|
||||
gst::debug!(CAT, imp: self, "Draining because of discontinuity");
|
||||
self.drain_packets(&settings, &mut state, true)?;
|
||||
}
|
||||
|
||||
state.audio_discont.resync(pts, num_samples);
|
||||
}
|
||||
|
||||
state.queued_bytes += buffer.buffer().size();
|
||||
state.queued_buffers.push_back(QueuedBuffer {
|
||||
id,
|
||||
buffer,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
self.drain_packets(&settings, &mut state, false)
|
||||
}
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
fn src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
let res = self.parent_src_query(query);
|
||||
if !res {
|
||||
return false;
|
||||
}
|
||||
|
||||
match query.view_mut() {
|
||||
gst::QueryViewMut::Latency(query) => {
|
||||
let (is_live, mut min, mut max) = query.result();
|
||||
let min_ptime = self.settings.lock().unwrap().min_ptime;
|
||||
min += min_ptime;
|
||||
max.opt_add_assign(min_ptime);
|
||||
query.set(is_live, min, max);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl RtpBaseAudioPay2 {
|
||||
/// Returns the minimum, maximum and chunk/multiple packet sizes
|
||||
fn calculate_packet_sizes(
|
||||
&self,
|
||||
settings: &Settings,
|
||||
state: &State,
|
||||
clock_rate: u32,
|
||||
bpf: usize,
|
||||
) -> (usize, usize, usize) {
|
||||
let min_pframes = settings
|
||||
.min_ptime
|
||||
.nseconds()
|
||||
.mul_div_ceil(clock_rate as u64, gst::ClockTime::SECOND.nseconds())
|
||||
.unwrap() as u32;
|
||||
let max_ptime = match (settings.max_ptime, state.max_ptime) {
|
||||
(Some(max_ptime), Some(caps_max_ptime)) => Some(cmp::min(max_ptime, caps_max_ptime)),
|
||||
(None, Some(max_ptime)) => Some(max_ptime),
|
||||
(Some(max_ptime), None) => Some(max_ptime),
|
||||
_ => None,
|
||||
};
|
||||
let max_pframes = max_ptime.map(|max_ptime| {
|
||||
max_ptime
|
||||
.nseconds()
|
||||
.mul_div_ceil(clock_rate as u64, gst::ClockTime::SECOND.nseconds())
|
||||
.unwrap() as u32
|
||||
});
|
||||
let pframes_multiple = cmp::max(
|
||||
1,
|
||||
settings
|
||||
.ptime_multiple
|
||||
.nseconds()
|
||||
.mul_div_ceil(clock_rate as u64, gst::ClockTime::SECOND.nseconds())
|
||||
.unwrap() as u32,
|
||||
);
|
||||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"min ptime {} (frames: {}), max ptime {} (frames: {}), ptime multiple {} (frames {})",
|
||||
settings.min_ptime,
|
||||
min_pframes,
|
||||
settings.max_ptime.display(),
|
||||
max_pframes.unwrap_or(0),
|
||||
settings.ptime_multiple,
|
||||
pframes_multiple,
|
||||
);
|
||||
|
||||
let psize_multiple = pframes_multiple as usize * bpf;
|
||||
|
||||
let mut max_packet_size = self.obj().max_payload_size() as usize;
|
||||
max_packet_size -= max_packet_size % psize_multiple;
|
||||
if let Some(max_pframes) = max_pframes {
|
||||
max_packet_size = cmp::min(max_pframes as usize * bpf, max_packet_size)
|
||||
}
|
||||
let mut min_packet_size = cmp::min(
|
||||
cmp::max(min_pframes as usize * bpf, psize_multiple),
|
||||
max_packet_size,
|
||||
);
|
||||
|
||||
if let Some(ptime) = state.ptime {
|
||||
let pframes = ptime
|
||||
.nseconds()
|
||||
.mul_div_ceil(clock_rate as u64, gst::ClockTime::SECOND.nseconds())
|
||||
.unwrap() as u32;
|
||||
|
||||
let psize = pframes as usize * bpf;
|
||||
min_packet_size = cmp::max(min_packet_size, psize);
|
||||
max_packet_size = cmp::min(max_packet_size, psize);
|
||||
}
|
||||
|
||||
(min_packet_size, max_packet_size, psize_multiple)
|
||||
}
|
||||
|
||||
fn drain_packets(
|
||||
&self,
|
||||
settings: &Settings,
|
||||
state: &mut State,
|
||||
force: bool,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
// Always set when caps are set
|
||||
let Some(clock_rate) = state.clock_rate else {
|
||||
return Ok(gst::FlowSuccess::Ok);
|
||||
};
|
||||
let bpf = state.bpf.unwrap();
|
||||
|
||||
let (min_packet_size, max_packet_size, psize_multiple) =
|
||||
self.calculate_packet_sizes(settings, state, clock_rate, bpf);
|
||||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"Currently {} bytes queued, min packet size {min_packet_size}, max packet size {max_packet_size}, force {force}",
|
||||
state.queued_bytes,
|
||||
);
|
||||
|
||||
while state.queued_bytes >= min_packet_size || (force && state.queued_bytes > 0) {
|
||||
let packet_size = {
|
||||
let mut packet_size = cmp::min(max_packet_size, state.queued_bytes);
|
||||
packet_size -= packet_size % psize_multiple;
|
||||
packet_size
|
||||
};
|
||||
|
||||
gst::trace!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"Creating packet of size {packet_size} ({} frames), marker {}",
|
||||
packet_size / bpf,
|
||||
state.audio_discont.next_output_offset().is_none(),
|
||||
);
|
||||
|
||||
// Set marker bit on the first packet after a discontinuity
|
||||
let mut packet_builder = rtp_types::RtpPacketBuilder::new()
|
||||
.marker_bit(state.audio_discont.next_output_offset().is_none());
|
||||
|
||||
let front = state.queued_buffers.front().unwrap();
|
||||
let start_id = front.id;
|
||||
let mut end_id = front.id;
|
||||
|
||||
// Fill payload from all relevant buffers and collect start/end ids that apply.
|
||||
let mut remaining_packet_size = packet_size;
|
||||
for buffer in state.queued_buffers.iter() {
|
||||
let this_buffer_payload_size =
|
||||
cmp::min(buffer.buffer.size() - buffer.offset, remaining_packet_size);
|
||||
|
||||
end_id = buffer.id;
|
||||
packet_builder = packet_builder
|
||||
.payload(&buffer.buffer[buffer.offset..][..this_buffer_payload_size]);
|
||||
|
||||
remaining_packet_size -= this_buffer_payload_size;
|
||||
if remaining_packet_size == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Then create the packet.
|
||||
self.obj().queue_packet(
|
||||
PacketToBufferRelation::IdsWithOffset {
|
||||
ids: start_id..=end_id,
|
||||
timestamp_offset: {
|
||||
if let Some(next_out_offset) = state.audio_discont.next_output_offset() {
|
||||
TimestampOffset::Rtp(next_out_offset)
|
||||
} else {
|
||||
TimestampOffset::Pts(gst::ClockTime::ZERO)
|
||||
}
|
||||
},
|
||||
},
|
||||
packet_builder,
|
||||
)?;
|
||||
|
||||
// And finally dequeue or update all currently queued buffers.
|
||||
let mut remaining_packet_size = packet_size;
|
||||
while remaining_packet_size > 0 {
|
||||
let buffer = state.queued_buffers.front_mut().unwrap();
|
||||
|
||||
if buffer.buffer.size() - buffer.offset > remaining_packet_size {
|
||||
buffer.offset += remaining_packet_size;
|
||||
remaining_packet_size = 0;
|
||||
} else {
|
||||
remaining_packet_size -= buffer.buffer.size() - buffer.offset;
|
||||
let _ = state.queued_buffers.pop_front();
|
||||
}
|
||||
}
|
||||
state.queued_bytes -= packet_size;
|
||||
state.audio_discont.process_output(packet_size / bpf);
|
||||
}
|
||||
|
||||
gst::trace!(CAT, imp: self, "Currently {} bytes / {} frames queued", state.queued_bytes, state.queued_bytes / bpf);
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper functions for public API.
|
||||
#[allow(dead_code)]
|
||||
impl RtpBaseAudioPay2 {
|
||||
pub(super) fn set_bpf(&self, bpf: usize) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.bpf = Some(bpf);
|
||||
}
|
||||
}
|
40
net/rtp/src/baseaudiopay/mod.rs
Normal file
40
net/rtp/src/baseaudiopay/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::basepay::RtpBasePay2Impl;
|
||||
|
||||
pub mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpBaseAudioPay2(ObjectSubclass<imp::RtpBaseAudioPay2>)
|
||||
@extends crate::basepay::RtpBasePay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `RtpBaseAudioPay2`.
|
||||
pub trait RtpBaseAudioPay2Ext: IsA<RtpBaseAudioPay2> {
|
||||
/// Sets the number of bytes per frame.
|
||||
///
|
||||
/// Should always be called together with `RtpBasePay2Ext::set_src_caps()`.
|
||||
fn set_bpf(&self, bpf: usize) {
|
||||
self.upcast_ref::<RtpBaseAudioPay2>().imp().set_bpf(bpf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: IsA<RtpBaseAudioPay2>> RtpBaseAudioPay2Ext for O {}
|
||||
|
||||
/// Trait to implement in `RtpBaseAudioPay2` subclasses.
|
||||
pub trait RtpBaseAudioPay2Impl: RtpBasePay2Impl {}
|
||||
|
||||
unsafe impl<T: RtpBaseAudioPay2Impl> IsSubclassable<T> for RtpBaseAudioPay2 {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
}
|
||||
}
|
1937
net/rtp/src/basedepay/imp.rs
Normal file
1937
net/rtp/src/basedepay/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
543
net/rtp/src/basedepay/mod.rs
Normal file
543
net/rtp/src/basedepay/mod.rs
Normal file
|
@ -0,0 +1,543 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::ops::{Range, RangeBounds, RangeInclusive};
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
pub mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpBaseDepay2(ObjectSubclass<imp::RtpBaseDepay2>)
|
||||
@extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `RtpBaseDepay2`.
|
||||
pub trait RtpBaseDepay2Ext: IsA<RtpBaseDepay2> {
|
||||
/// Sends a caps event with the given caps downstream before the next output buffer.
|
||||
fn set_src_caps(&self, src_caps: &gst::Caps) {
|
||||
assert!(src_caps.is_fixed());
|
||||
self.upcast_ref::<RtpBaseDepay2>()
|
||||
.imp()
|
||||
.set_src_caps(src_caps);
|
||||
}
|
||||
|
||||
/// Drop the packets of the given packet range.
|
||||
///
|
||||
/// This should be called when packets are dropped because they either don't make up any output
|
||||
/// buffer or because they're corrupted in some way.
|
||||
///
|
||||
/// All pending packets up to the end of the range are dropped, i.e. the start of the range is
|
||||
/// irrelevant.
|
||||
///
|
||||
/// It is not necessary to call this as part of `drain()` or `flush()` as all still pending packets are
|
||||
/// considered dropped afterwards.
|
||||
///
|
||||
/// The next buffer that is finished will automatically get the `DISCONT` flag set.
|
||||
// FIXME: Allow subclasses to drop explicit packet ranges while keeping older packets around to
|
||||
// allow for continuing to reconstruct a frame despite broken packets in the middle.
|
||||
fn drop_packets(&self, ext_seqnum: impl RangeBounds<u64>) {
|
||||
self.upcast_ref::<RtpBaseDepay2>()
|
||||
.imp()
|
||||
.drop_packets(ext_seqnum)
|
||||
}
|
||||
|
||||
/// Drop a single packet
|
||||
///
|
||||
/// Convenience wrapper for calling drop_packets() for a single packet.
|
||||
fn drop_packet(&self, packet: &Packet) {
|
||||
self.drop_packets(packet.ext_seqnum()..=packet.ext_seqnum())
|
||||
}
|
||||
|
||||
/// Queue a buffer made from a given range of packet seqnums and timestamp offsets.
|
||||
///
|
||||
/// All buffers that are queued during one call of `handle_packet()` are collected in a
|
||||
/// single buffer list and forwarded once `handle_packet()` has returned with a successful flow
|
||||
/// return or `finish_pending_buffers()` was called.
|
||||
///
|
||||
/// All pending packets for which buffers were queued are released once `handle_packets()`
|
||||
/// returned except for the last one. This means that it is possible for subclasses to queue a
|
||||
/// buffer and output a remaining chunk of that buffer together with data from the next buffer.
|
||||
///
|
||||
/// If passing `OutOfBand` then the buffer is assumed to be produced using some other data,
|
||||
/// e.g. from the caps, and not associated with any packets. In that case it will be pushed
|
||||
/// right before the next buffer with the timestamp of that buffer, or at EOS with the
|
||||
/// timestamp of the previous buffer.
|
||||
///
|
||||
/// In all other cases a seqnum range is provided and needs to be valid.
|
||||
///
|
||||
/// If the seqnum range doesn't start with the first pending packet then all packets up to the
|
||||
/// first one given in the range are considered dropped.
|
||||
///
|
||||
/// Together with the seqnum range it is possible to provide a timestamp offset relative to
|
||||
/// which the outgoing buffers's timestamp should be set.
|
||||
///
|
||||
/// The timestamp offset is provided in nanoseconds relative to the PTS of the packet that the
|
||||
/// first seqnum refers to. This mode is mostly useful for subclasses that consume a packet
|
||||
/// with multiple frames and send out one buffer per frame.
|
||||
///
|
||||
/// Both a PTS and DTS offset can be provided. If no DTS offset is provided then no DTS will be
|
||||
/// set at all. If no PTS offset is provided then the first buffer for a given start seqnum
|
||||
/// will get the PTS of the corresponding packet and all following buffers that start with the
|
||||
/// same seqnum will get no PTS set.
|
||||
///
|
||||
/// Note that the DTS offset is relative to the final PTS and as such should not include the
|
||||
/// PTS offset.
|
||||
fn queue_buffer(
|
||||
&self,
|
||||
packet_to_buffer_relation: PacketToBufferRelation,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.upcast_ref::<RtpBaseDepay2>()
|
||||
.imp()
|
||||
.queue_buffer(packet_to_buffer_relation, buffer)
|
||||
}
|
||||
|
||||
/// Finish currently pending buffers and push them downstream in a single buffer list.
|
||||
fn finish_pending_buffers(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.upcast_ref::<RtpBaseDepay2>()
|
||||
.imp()
|
||||
.finish_pending_buffers()
|
||||
}
|
||||
|
||||
/// Returns a reference to the sink pad.
|
||||
fn sink_pad(&self) -> &gst::Pad {
|
||||
self.upcast_ref::<RtpBaseDepay2>().imp().sink_pad()
|
||||
}
|
||||
|
||||
/// Returns a reference to the src pad.
|
||||
fn src_pad(&self) -> &gst::Pad {
|
||||
self.upcast_ref::<RtpBaseDepay2>().imp().src_pad()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: IsA<RtpBaseDepay2>> RtpBaseDepay2Ext for O {}
|
||||
|
||||
/// Trait to implement in `RtpBaseDepay2` subclasses.
|
||||
pub trait RtpBaseDepay2Impl: ElementImpl {
|
||||
/// By default only metas without any tags are copied. Adding tags here will also copy the
|
||||
/// metas that *only* have exactly one of these tags.
|
||||
///
|
||||
/// If more complex copying of metas is needed then [`RtpBaseDepay2Impl::transform_meta`] has
|
||||
/// to be implemented.
|
||||
const ALLOWED_META_TAGS: &'static [&'static str] = &[];
|
||||
|
||||
/// Called when streaming starts (READY -> PAUSED state change)
|
||||
///
|
||||
/// Optional, can be used to initialise streaming state.
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_start()
|
||||
}
|
||||
|
||||
/// Called after streaming has stopped (PAUSED -> READY state change)
|
||||
///
|
||||
/// Optional, can be used to clean up streaming state.
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_stop()
|
||||
}
|
||||
|
||||
/// Called when new caps are received on the sink pad.
|
||||
///
|
||||
/// Can be used to configure the caps on the src pad or to configure caps-specific state.
|
||||
/// If draining is necessary because of the caps change then the subclass will have to do that.
|
||||
///
|
||||
/// Optional, by default does nothing.
|
||||
fn set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
self.parent_set_sink_caps(caps)
|
||||
}
|
||||
|
||||
/// Called whenever a new packet is available.
|
||||
fn handle_packet(&self, packet: &Packet) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_handle_packet(packet)
|
||||
}
|
||||
|
||||
/// Called whenever a discontinuity or EOS is observed.
|
||||
///
|
||||
/// The subclass should output any pending buffers it can output at this point.
|
||||
///
|
||||
/// This will be followed by a call to [`Self::flush`].
|
||||
///
|
||||
/// Optional, by default drops all still pending packets and forwards all still pending buffers
|
||||
/// with the last known timestamp.
|
||||
fn drain(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_drain()
|
||||
}
|
||||
|
||||
/// Called on `FlushStop` or whenever all pending data should simply be discarded.
|
||||
///
|
||||
/// The subclass should reset its internal state as necessary.
|
||||
///
|
||||
/// Optional.
|
||||
fn flush(&self) {
|
||||
self.parent_flush()
|
||||
}
|
||||
|
||||
/// Called whenever a new event arrives on the sink pad.
|
||||
///
|
||||
/// Optional, by default does the standard event handling of the base class.
|
||||
fn sink_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_sink_event(event)
|
||||
}
|
||||
|
||||
/// Called whenever a new event arrives on the src pad.
|
||||
///
|
||||
/// Optional, by default does the standard event handling of the base class.
|
||||
fn src_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_src_event(event)
|
||||
}
|
||||
|
||||
/// Called whenever a new query arrives on the sink pad.
|
||||
///
|
||||
/// Optional, by default does the standard query handling of the base class.
|
||||
fn sink_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
self.parent_sink_query(query)
|
||||
}
|
||||
|
||||
/// Called whenever a new query arrives on the src pad.
|
||||
///
|
||||
/// Optional, by default does the standard query handling of the base class.
|
||||
fn src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
self.parent_src_query(query)
|
||||
}
|
||||
|
||||
/// Called whenever a meta from an input buffer has to be copied to the output buffer.
|
||||
///
|
||||
/// Optional, by default simply copies over all metas.
|
||||
fn transform_meta(
|
||||
&self,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
) {
|
||||
self.parent_transform_meta(in_buf, meta, out_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `RtpBaseDepay2Impl`, specifically methods for chaining
|
||||
/// up to the parent implementation of virtual methods.
|
||||
pub trait RtpBaseDepay2ImplExt: RtpBaseDepay2Impl {
|
||||
fn parent_set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.set_sink_caps)(self.obj().unsafe_cast_ref(), caps)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.start)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.stop)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_handle_packet(&self, packet: &Packet) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.handle_packet)(self.obj().unsafe_cast_ref(), packet)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_drain(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.drain)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_flush(&self) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.flush)(self.obj().unsafe_cast_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_sink_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.sink_event)(self.obj().unsafe_cast_ref(), event)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_src_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.src_event)(self.obj().unsafe_cast_ref(), event)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_sink_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.sink_query)(self.obj().unsafe_cast_ref(), query)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.src_query)(self.obj().unsafe_cast_ref(), query)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_transform_meta(
|
||||
&self,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.transform_meta)(self.obj().unsafe_cast_ref(), in_buf, meta, out_buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RtpBaseDepay2Impl> RtpBaseDepay2ImplExt for T {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Packet {
|
||||
buffer: gst::MappedBuffer<gst::buffer::Readable>,
|
||||
|
||||
discont: bool,
|
||||
ext_seqnum: u64,
|
||||
ext_timestamp: u64,
|
||||
marker: bool,
|
||||
payload_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
pub fn ext_seqnum(&self) -> u64 {
|
||||
self.ext_seqnum
|
||||
}
|
||||
|
||||
pub fn ext_timestamp(&self) -> u64 {
|
||||
self.ext_timestamp
|
||||
}
|
||||
|
||||
pub fn marker_bit(&self) -> bool {
|
||||
self.marker
|
||||
}
|
||||
|
||||
pub fn discont(&self) -> bool {
|
||||
self.discont
|
||||
}
|
||||
|
||||
pub fn pts(&self) -> Option<gst::ClockTime> {
|
||||
self.buffer.buffer().pts()
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> &[u8] {
|
||||
&self.buffer[self.payload_range.clone()]
|
||||
}
|
||||
|
||||
pub fn payload_buffer(&self) -> gst::Buffer {
|
||||
self.buffer
|
||||
.buffer()
|
||||
.copy_region(
|
||||
gst::BufferCopyFlags::MEMORY,
|
||||
self.payload_range.start..self.payload_range.end,
|
||||
)
|
||||
.expect("Failed copying buffer")
|
||||
}
|
||||
|
||||
/// Note: This function will panic if the range is out of bounds.
|
||||
pub fn payload_subbuffer(&self, range: impl RangeBounds<usize>) -> gst::Buffer {
|
||||
let range_start = match range.start_bound() {
|
||||
std::ops::Bound::Included(&start) => start,
|
||||
std::ops::Bound::Excluded(&start) => start.checked_add(1).unwrap(),
|
||||
std::ops::Bound::Unbounded => 0,
|
||||
}
|
||||
.checked_add(self.payload_range.start)
|
||||
.unwrap();
|
||||
|
||||
let range_end = match range.end_bound() {
|
||||
std::ops::Bound::Included(&range_end) => range_end
|
||||
.checked_add(self.payload_range.start)
|
||||
.and_then(|v| v.checked_add(1))
|
||||
.unwrap(),
|
||||
std::ops::Bound::Excluded(&range_end) => {
|
||||
range_end.checked_add(self.payload_range.start).unwrap()
|
||||
}
|
||||
std::ops::Bound::Unbounded => self.payload_range.end,
|
||||
};
|
||||
|
||||
self.buffer
|
||||
.buffer()
|
||||
.copy_region(gst::BufferCopyFlags::MEMORY, range_start..range_end)
|
||||
.expect("Failed to create subbuffer")
|
||||
}
|
||||
|
||||
/// Note: For offset with unspecified length just use `payload_subbuffer(off..)`.
|
||||
/// Note: This function will panic if the offset or length are out of bounds.
|
||||
pub fn payload_subbuffer_from_offset_with_length(
|
||||
&self,
|
||||
start: usize,
|
||||
length: usize,
|
||||
) -> gst::Buffer {
|
||||
self.payload_subbuffer(start..start + length)
|
||||
}
|
||||
}
|
||||
|
||||
/// Class struct for `RtpBaseDepay2`.
|
||||
#[repr(C)]
|
||||
pub struct Class {
|
||||
parent: gst::ffi::GstElementClass,
|
||||
|
||||
start: fn(&RtpBaseDepay2) -> Result<(), gst::ErrorMessage>,
|
||||
stop: fn(&RtpBaseDepay2) -> Result<(), gst::ErrorMessage>,
|
||||
|
||||
set_sink_caps: fn(&RtpBaseDepay2, caps: &gst::Caps) -> bool,
|
||||
handle_packet: fn(&RtpBaseDepay2, packet: &Packet) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
drain: fn(&RtpBaseDepay2) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
flush: fn(&RtpBaseDepay2),
|
||||
|
||||
sink_event: fn(&RtpBaseDepay2, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
src_event: fn(&RtpBaseDepay2, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
|
||||
sink_query: fn(&RtpBaseDepay2, query: &mut gst::QueryRef) -> bool,
|
||||
src_query: fn(&RtpBaseDepay2, query: &mut gst::QueryRef) -> bool,
|
||||
|
||||
transform_meta: fn(
|
||||
&RtpBaseDepay2,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
),
|
||||
|
||||
allowed_meta_tags: &'static [&'static str],
|
||||
}
|
||||
|
||||
unsafe impl ClassStruct for Class {
|
||||
type Type = imp::RtpBaseDepay2;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Class {
|
||||
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(&self.parent as *const _ as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: RtpBaseDepay2Impl> IsSubclassable<T> for RtpBaseDepay2 {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
|
||||
let class = class.as_mut();
|
||||
|
||||
class.start = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.start()
|
||||
};
|
||||
|
||||
class.stop = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.stop()
|
||||
};
|
||||
|
||||
class.set_sink_caps = |obj, caps| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.set_sink_caps(caps)
|
||||
};
|
||||
|
||||
class.handle_packet = |obj, packet| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.handle_packet(packet)
|
||||
};
|
||||
|
||||
class.drain = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.drain()
|
||||
};
|
||||
|
||||
class.flush = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.flush()
|
||||
};
|
||||
|
||||
class.sink_event = |obj, event| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.sink_event(event)
|
||||
};
|
||||
|
||||
class.src_event = |obj, event| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.src_event(event)
|
||||
};
|
||||
|
||||
class.sink_query = |obj, query| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.sink_query(query)
|
||||
};
|
||||
|
||||
class.src_query = |obj, query| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.src_query(query)
|
||||
};
|
||||
|
||||
class.transform_meta = |obj, in_buf, meta, out_buf| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.transform_meta(in_buf, meta, out_buf)
|
||||
};
|
||||
|
||||
class.allowed_meta_tags = T::ALLOWED_META_TAGS;
|
||||
}
|
||||
}
|
||||
|
||||
/// Timestamp offset between this buffer and the reference packet.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum TimestampOffset {
|
||||
/// Offset in nanoseconds relative to the first seqnum this buffer belongs to.
|
||||
Pts(gst::Signed<gst::ClockTime>),
|
||||
/// Offset in nanoseconds relative to the first seqnum this buffer belongs to.
|
||||
PtsAndDts(gst::Signed<gst::ClockTime>, gst::Signed<gst::ClockTime>),
|
||||
}
|
||||
|
||||
/// Relation between queued buffer and input packet seqnums.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PacketToBufferRelation {
|
||||
Seqnums(RangeInclusive<u64>),
|
||||
SeqnumsWithOffset {
|
||||
seqnums: RangeInclusive<u64>,
|
||||
timestamp_offset: TimestampOffset,
|
||||
},
|
||||
OutOfBand,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Packet> for PacketToBufferRelation {
|
||||
fn from(packet: &'a Packet) -> Self {
|
||||
PacketToBufferRelation::Seqnums(packet.ext_seqnum()..=packet.ext_seqnum())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for PacketToBufferRelation {
|
||||
fn from(ext_seqnum: u64) -> Self {
|
||||
PacketToBufferRelation::Seqnums(ext_seqnum..=ext_seqnum)
|
||||
}
|
||||
}
|
2115
net/rtp/src/basepay/imp.rs
Normal file
2115
net/rtp/src/basepay/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
494
net/rtp/src/basepay/mod.rs
Normal file
494
net/rtp/src/basepay/mod.rs
Normal file
|
@ -0,0 +1,494 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::ops::{RangeBounds, RangeInclusive};
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
pub mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpBasePay2(ObjectSubclass<imp::RtpBasePay2>)
|
||||
@extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `RtpBasePay2`.
|
||||
pub trait RtpBasePay2Ext: IsA<RtpBasePay2> {
|
||||
/// Sends a caps event with the given caps downstream before the next output buffer.
|
||||
///
|
||||
/// The caps must be `application/x-rtp` and contain the `clock-rate` field with a suitable
|
||||
/// clock-rate for this stream.
|
||||
///
|
||||
/// The caps can be unfixed and will be passed through `RtpBasePay2Impl::negotiate()` to
|
||||
/// negotiate caps with downstream, and finally fixate them.
|
||||
fn set_src_caps(&self, src_caps: &gst::Caps) {
|
||||
self.upcast_ref::<RtpBasePay2>()
|
||||
.imp()
|
||||
.set_src_caps(src_caps);
|
||||
}
|
||||
|
||||
/// Drop the buffers from the given buffer range.
|
||||
///
|
||||
/// This should be called when input buffers are dropped because they are not included in any
|
||||
/// output packet.
|
||||
///
|
||||
/// All pending buffers up to the end of the range are dropped, i.e. the start of the range is
|
||||
/// irrelevant.
|
||||
fn drop_buffers(&self, ids: impl RangeBounds<u64>) {
|
||||
self.upcast_ref::<RtpBasePay2>().imp().drop_buffers(ids)
|
||||
}
|
||||
|
||||
/// Queue an RTP packet made from a given range of input buffer ids and timestamp offset.
|
||||
///
|
||||
/// All packets that are queued during one call of `handle_buffer()` are collected in a
|
||||
/// single buffer list and forwarded once `handle_buffer()` has returned with a successful flow
|
||||
/// return or `finish_pending_packets()` was called.
|
||||
///
|
||||
/// All pending buffers for which packets were queued are released once `handle_buffer()`
|
||||
/// returned except for the last one. This means that it is possible for subclasses to queue a
|
||||
/// buffer and output a remaining chunk of that buffer together with data from the next buffer.
|
||||
///
|
||||
/// If passing `OutOfBand` then the packet is assumed to be produced using some other data,
|
||||
/// e.g. from the caps, and not associated with any packets. In that case it will be pushed
|
||||
/// right before the next packet with the timestamp of that packet, or at EOS with the
|
||||
/// timestamp of the previous packet.
|
||||
///
|
||||
/// In all other cases a buffer id range is provided and needs to be valid.
|
||||
///
|
||||
/// If the id range doesn't start with the first pending buffer then all buffers up to the
|
||||
/// first one given in the range are considered dropped.
|
||||
///
|
||||
/// Together with the buffer ids it is possible to provide a timestamp offset relative to which
|
||||
/// the outgoing RTP timestamp and GStreamer PTS should be set.
|
||||
///
|
||||
/// The timestamp offset can be provided in two ways:
|
||||
///
|
||||
/// * Nanoseconds relative to the PTS of the buffer that the first id refers to. This mode is
|
||||
/// mostly useful for subclasses that consume a buffer with multiple frames and send out
|
||||
/// one packet per frame.
|
||||
///
|
||||
/// * RTP clock-rate units (without wrap-arounds) relative to the last buffer that had no
|
||||
/// timestamp offset given in RTP clock-rate units. In this mode the subclass needs to be
|
||||
/// careful to handle discontinuities of any sort correctly. Also, the subclass needs to
|
||||
/// provide an explicit timestamp (either by setting no offset or by setting a PTS-based
|
||||
/// offset) for the first packet ever and after every `drain()` or `flush()`.
|
||||
fn queue_packet(
|
||||
&self,
|
||||
packet_to_buffer_relation: PacketToBufferRelation,
|
||||
packet: rtp_types::RtpPacketBuilder<&[u8], &[u8]>,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.upcast_ref::<RtpBasePay2>()
|
||||
.imp()
|
||||
.queue_packet(packet_to_buffer_relation, packet)
|
||||
}
|
||||
|
||||
/// Finish currently pending packets and push them downstream in a single buffer list.
|
||||
fn finish_pending_packets(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.upcast_ref::<RtpBasePay2>()
|
||||
.imp()
|
||||
.finish_pending_packets()
|
||||
}
|
||||
|
||||
/// Returns a reference to the sink pad.
|
||||
fn sink_pad(&self) -> &gst::Pad {
|
||||
self.upcast_ref::<RtpBasePay2>().imp().sink_pad()
|
||||
}
|
||||
|
||||
/// Returns a reference to the src pad.
|
||||
fn src_pad(&self) -> &gst::Pad {
|
||||
self.upcast_ref::<RtpBasePay2>().imp().src_pad()
|
||||
}
|
||||
|
||||
/// Returns the currently configured MTU.
|
||||
fn mtu(&self) -> u32 {
|
||||
self.upcast_ref::<RtpBasePay2>().imp().mtu()
|
||||
}
|
||||
|
||||
/// Returns the maximum available payload size.
|
||||
fn max_payload_size(&self) -> u32 {
|
||||
self.upcast_ref::<RtpBasePay2>().imp().max_payload_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: IsA<RtpBasePay2>> RtpBasePay2Ext for O {}
|
||||
|
||||
/// Trait to implement in `RtpBasePay2` subclasses.
|
||||
pub trait RtpBasePay2Impl: ElementImpl {
|
||||
/// Drop buffers with `HEADER` flag.
|
||||
const DROP_HEADER_BUFFERS: bool = false;
|
||||
|
||||
/// By default only metas without any tags are copied. Adding tags here will also copy the
|
||||
/// metas that *only* have exactly one of these tags.
|
||||
///
|
||||
/// If more complex copying of metas is needed then [`RtpBasePay2Impl::transform_meta`] has
|
||||
/// to be implemented.
|
||||
const ALLOWED_META_TAGS: &'static [&'static str] = &[];
|
||||
|
||||
/// Called when streaming starts (READY -> PAUSED state change)
|
||||
///
|
||||
/// Optional, can be used to initialise streaming state.
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_start()
|
||||
}
|
||||
|
||||
/// Called after streaming has stopped (PAUSED -> READY state change)
|
||||
///
|
||||
/// Optional, can be used to clean up streaming state.
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.parent_stop()
|
||||
}
|
||||
|
||||
/// Called when new caps are received on the sink pad.
|
||||
///
|
||||
/// Can be used to configure the caps on the src pad or to configure caps-specific state.
|
||||
///
|
||||
/// Optional, by default does nothing.
|
||||
fn set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
self.parent_set_sink_caps(caps)
|
||||
}
|
||||
|
||||
/// Called when new caps are configured on the source pad and whenever renegotiation has to happen.
|
||||
///
|
||||
/// The `src_caps` are the caps passed into `set_src_caps()` before, intersected with the
|
||||
/// supported caps by the peer, and will have to be fixated.
|
||||
///
|
||||
/// Optional, by default sets the `payload` (pt) and `ssrc` fields, and negotiates RTP header
|
||||
/// extensions with downstream, and finally fixates the caps and configures them on the source
|
||||
/// pad.
|
||||
fn negotiate(&self, src_caps: gst::Caps) {
|
||||
assert!(src_caps.is_writable());
|
||||
self.parent_negotiate(src_caps);
|
||||
}
|
||||
|
||||
/// Called whenever a new buffer is available.
|
||||
fn handle_buffer(
|
||||
&self,
|
||||
buffer: &gst::Buffer,
|
||||
id: u64,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_handle_buffer(buffer, id)
|
||||
}
|
||||
|
||||
/// Called whenever a discontinuity or EOS is observed.
|
||||
///
|
||||
/// The subclass should output any pending buffers it can output at this point.
|
||||
///
|
||||
/// This will be followed by a call to [`Self::flush`].
|
||||
///
|
||||
/// Optional, by default drops all still pending buffers and forwards all still pending packets
|
||||
/// with the last known timestamp.
|
||||
fn drain(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_drain()
|
||||
}
|
||||
|
||||
/// Called on `FlushStop` or whenever all pending data should simply be discarded.
|
||||
///
|
||||
/// The subclass should reset its internal state as necessary.
|
||||
///
|
||||
/// Optional.
|
||||
fn flush(&self) {
|
||||
self.parent_flush()
|
||||
}
|
||||
|
||||
/// Called whenever a new event arrives on the sink pad.
|
||||
///
|
||||
/// Optional, by default does the standard event handling of the base class.
|
||||
fn sink_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_sink_event(event)
|
||||
}
|
||||
|
||||
/// Called whenever a new event arrives on the src pad.
|
||||
///
|
||||
/// Optional, by default does the standard event handling of the base class.
|
||||
fn src_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
self.parent_src_event(event)
|
||||
}
|
||||
|
||||
/// Called whenever a new query arrives on the sink pad.
|
||||
///
|
||||
/// Optional, by default does the standard query handling of the base class.
|
||||
fn sink_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
self.parent_sink_query(query)
|
||||
}
|
||||
|
||||
/// Called whenever a new query arrives on the src pad.
|
||||
///
|
||||
/// Optional, by default does the standard query handling of the base class.
|
||||
fn src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
self.parent_src_query(query)
|
||||
}
|
||||
|
||||
/// Called whenever a meta from an input buffer has to be copied to the output buffer.
|
||||
///
|
||||
/// Optional, by default simply copies over all metas.
|
||||
fn transform_meta(
|
||||
&self,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
) {
|
||||
self.parent_transform_meta(in_buf, meta, out_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait containing extension methods for `RtpBasePay2Impl`, specifically methods for chaining
|
||||
/// up to the parent implementation of virtual methods.
|
||||
pub trait RtpBasePay2ImplExt: RtpBasePay2Impl {
|
||||
fn parent_set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.set_sink_caps)(self.obj().unsafe_cast_ref(), caps)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_negotiate(&self, src_caps: gst::Caps) {
|
||||
assert!(src_caps.is_writable());
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.negotiate)(self.obj().unsafe_cast_ref(), src_caps);
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.start)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.stop)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_handle_buffer(
|
||||
&self,
|
||||
buffer: &gst::Buffer,
|
||||
id: u64,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.handle_buffer)(self.obj().unsafe_cast_ref(), buffer, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_drain(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.drain)(self.obj().unsafe_cast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_flush(&self) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.flush)(self.obj().unsafe_cast_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_sink_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.sink_event)(self.obj().unsafe_cast_ref(), event)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_src_event(&self, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.src_event)(self.obj().unsafe_cast_ref(), event)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_sink_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.sink_query)(self.obj().unsafe_cast_ref(), query)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_src_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.src_query)(self.obj().unsafe_cast_ref(), query)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_transform_meta(
|
||||
&self,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
) {
|
||||
unsafe {
|
||||
let data = Self::type_data();
|
||||
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||
(parent_class.transform_meta)(self.obj().unsafe_cast_ref(), in_buf, meta, out_buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RtpBasePay2Impl> RtpBasePay2ImplExt for T {}
|
||||
|
||||
/// Class struct for `RtpBasePay2`.
|
||||
#[repr(C)]
|
||||
pub struct Class {
|
||||
parent: gst::ffi::GstElementClass,
|
||||
|
||||
start: fn(&RtpBasePay2) -> Result<(), gst::ErrorMessage>,
|
||||
stop: fn(&RtpBasePay2) -> Result<(), gst::ErrorMessage>,
|
||||
|
||||
set_sink_caps: fn(&RtpBasePay2, caps: &gst::Caps) -> bool,
|
||||
negotiate: fn(&RtpBasePay2, src_caps: gst::Caps),
|
||||
handle_buffer:
|
||||
fn(&RtpBasePay2, buffer: &gst::Buffer, id: u64) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
drain: fn(&RtpBasePay2) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
flush: fn(&RtpBasePay2),
|
||||
|
||||
sink_event: fn(&RtpBasePay2, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
src_event: fn(&RtpBasePay2, event: gst::Event) -> Result<gst::FlowSuccess, gst::FlowError>,
|
||||
|
||||
sink_query: fn(&RtpBasePay2, query: &mut gst::QueryRef) -> bool,
|
||||
src_query: fn(&RtpBasePay2, query: &mut gst::QueryRef) -> bool,
|
||||
|
||||
transform_meta: fn(
|
||||
&RtpBasePay2,
|
||||
in_buf: &gst::BufferRef,
|
||||
meta: &gst::MetaRef<gst::Meta>,
|
||||
out_buf: &mut gst::BufferRef,
|
||||
),
|
||||
|
||||
allowed_meta_tags: &'static [&'static str],
|
||||
drop_header_buffers: bool,
|
||||
}
|
||||
|
||||
unsafe impl ClassStruct for Class {
|
||||
type Type = imp::RtpBasePay2;
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Class {
|
||||
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(&self.parent as *const _ as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: RtpBasePay2Impl> IsSubclassable<T> for RtpBasePay2 {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
|
||||
let class = class.as_mut();
|
||||
|
||||
class.start = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.start()
|
||||
};
|
||||
|
||||
class.stop = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.stop()
|
||||
};
|
||||
|
||||
class.set_sink_caps = |obj, caps| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.set_sink_caps(caps)
|
||||
};
|
||||
|
||||
class.negotiate = |obj, src_caps| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.negotiate(src_caps)
|
||||
};
|
||||
|
||||
class.handle_buffer = |obj, buffer, id| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.handle_buffer(buffer, id)
|
||||
};
|
||||
|
||||
class.drain = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.drain()
|
||||
};
|
||||
|
||||
class.flush = |obj| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.flush()
|
||||
};
|
||||
|
||||
class.sink_event = |obj, event| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.sink_event(event)
|
||||
};
|
||||
|
||||
class.src_event = |obj, event| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.src_event(event)
|
||||
};
|
||||
|
||||
class.sink_query = |obj, query| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.sink_query(query)
|
||||
};
|
||||
|
||||
class.src_query = |obj, query| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.src_query(query)
|
||||
};
|
||||
|
||||
class.transform_meta = |obj, in_buf, meta, out_buf| unsafe {
|
||||
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||
imp.transform_meta(in_buf, meta, out_buf)
|
||||
};
|
||||
|
||||
class.allowed_meta_tags = T::ALLOWED_META_TAGS;
|
||||
class.drop_header_buffers = T::DROP_HEADER_BUFFERS;
|
||||
}
|
||||
}
|
||||
|
||||
/// Timestamp offset between this packet and the reference.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum TimestampOffset {
|
||||
/// Offset in nanoseconds relative to the first buffer id this packet belongs to.
|
||||
Pts(gst::ClockTime),
|
||||
/// Offset in RTP clock-time units relative to the last packet that had offset given in RTP
|
||||
/// clock-rate units.
|
||||
Rtp(u64),
|
||||
}
|
||||
|
||||
/// Relation between queued packet and input buffer ids.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum PacketToBufferRelation {
|
||||
Ids(RangeInclusive<u64>),
|
||||
IdsWithOffset {
|
||||
ids: RangeInclusive<u64>,
|
||||
timestamp_offset: TimestampOffset,
|
||||
},
|
||||
OutOfBand,
|
||||
}
|
||||
|
||||
impl From<u64> for PacketToBufferRelation {
|
||||
fn from(id: u64) -> Self {
|
||||
PacketToBufferRelation::Ids(id..=id)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
//
|
||||
// Copyright (C) 2022 Vivienne Watermeier <vwatermeier@igalia.com>
|
||||
// Copyright (C) 2022-24 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
|
@ -18,11 +19,36 @@ use gst::glib;
|
|||
mod av1;
|
||||
mod gcc;
|
||||
|
||||
mod audio_discont;
|
||||
mod baseaudiopay;
|
||||
mod basedepay;
|
||||
mod basepay;
|
||||
|
||||
mod pcmau;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
av1::depay::register(plugin)?;
|
||||
av1::pay::register(plugin)?;
|
||||
gcc::register(plugin)?;
|
||||
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
use gst::prelude::*;
|
||||
|
||||
crate::basepay::RtpBasePay2::static_type() // make base classes available in docs
|
||||
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
crate::basedepay::RtpBaseDepay2::static_type()
|
||||
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
crate::baseaudiopay::RtpBaseAudioPay2::static_type()
|
||||
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
|
||||
pcmau::depay::register(plugin)?;
|
||||
pcmau::pay::register(plugin)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
286
net/rtp/src/pcmau/depay/imp.rs
Normal file
286
net/rtp/src/pcmau/depay/imp.rs
Normal file
|
@ -0,0 +1,286 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::basedepay::RtpBaseDepay2Ext;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmauDepay {
|
||||
state: AtomicRefCell<State>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
clock_rate: Option<u32>,
|
||||
}
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"rtppcmaudepay2",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("RTP PCMA/PCMU Depayloader"),
|
||||
)
|
||||
});
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmauDepay {
|
||||
const NAME: &'static str = "GstRtpPcmauDepay2";
|
||||
type Type = super::RtpPcmauDepay;
|
||||
type ParentType = crate::basedepay::RtpBaseDepay2;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmauDepay {}
|
||||
|
||||
impl GstObjectImpl for RtpPcmauDepay {}
|
||||
|
||||
impl ElementImpl for RtpPcmauDepay {}
|
||||
|
||||
impl crate::basedepay::RtpBaseDepay2Impl for RtpPcmauDepay {
|
||||
const ALLOWED_META_TAGS: &'static [&'static str] = &["audio"];
|
||||
|
||||
fn set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
let s = caps.structure(0).unwrap();
|
||||
|
||||
let clock_rate = s.get::<i32>("clock-rate").unwrap_or(8000);
|
||||
|
||||
let src_caps = gst::Caps::builder(
|
||||
if self.obj().type_() == super::RtpPcmaDepay::static_type() {
|
||||
"audio/x-alaw"
|
||||
} else {
|
||||
"audio/x-mulaw"
|
||||
},
|
||||
)
|
||||
.field("channels", 1i32)
|
||||
.field("rate", clock_rate)
|
||||
.build();
|
||||
|
||||
self.state.borrow_mut().clock_rate = Some(clock_rate as u32);
|
||||
|
||||
self.obj().set_src_caps(&src_caps);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_packet(
|
||||
&self,
|
||||
packet: &crate::basedepay::Packet,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let mut buffer = packet.payload_buffer();
|
||||
|
||||
let state = self.state.borrow();
|
||||
// Always set when caps are set
|
||||
let clock_rate = state.clock_rate.unwrap();
|
||||
|
||||
let buffer_ref = buffer.get_mut().unwrap();
|
||||
buffer_ref.set_duration(
|
||||
(buffer_ref.size() as u64)
|
||||
.mul_div_floor(*gst::ClockTime::SECOND, clock_rate as u64)
|
||||
.map(gst::ClockTime::from_nseconds),
|
||||
);
|
||||
|
||||
// mark start of talkspurt with RESYNC
|
||||
if packet.marker_bit() {
|
||||
buffer_ref.set_flags(gst::BufferFlags::RESYNC);
|
||||
}
|
||||
|
||||
gst::trace!(CAT, imp: self, "Finishing buffer {buffer:?}");
|
||||
|
||||
self.obj().queue_buffer(packet.into(), buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SECTION:element-rtppcmadepay2
|
||||
* @see_also: rtppcmapay2, rtppcmupay2, rtppcmudepay2, alawenc, alawdec
|
||||
*
|
||||
* Extracts A-law encoded audio from RTP packets as per [RFC 3551][rfc-3551].
|
||||
*
|
||||
* [rfc-3551]: https://www.rfc-editor.org/rfc/rfc3551.html#section-4.5.14
|
||||
*
|
||||
* ## Example pipeline
|
||||
*
|
||||
* |[
|
||||
* gst-launch-1.0 udpsrc caps='application/x-rtp, media=audio, clock-rate=8000, payload=8' ! rtpjitterbuffer latency=50 ! rtppcmadepay2 ! alawdec ! audioconvert ! audioresample ! autoaudiosink
|
||||
* ]| This will depayload an incoming RTP A-law audio stream. You can use the #rtppcmapay2 and
|
||||
* alawenc elements to create such an RTP stream.
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmaDepay;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmaDepay {
|
||||
const NAME: &'static str = "GstRtpPcmaDepay2";
|
||||
type Type = super::RtpPcmaDepay;
|
||||
type ParentType = super::RtpPcmauDepay;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmaDepay {}
|
||||
|
||||
impl GstObjectImpl for RtpPcmaDepay {}
|
||||
|
||||
impl ElementImpl for RtpPcmaDepay {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"RTP PCMA Depayloader",
|
||||
"Codec/Depayloader/Network/RTP",
|
||||
"Depayload A-law from RTP packets (RFC 3551)",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder_full()
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", 8i32)
|
||||
.field("clock-rate", 8000i32)
|
||||
.build(),
|
||||
)
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("clock-rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.field("encoding-name", "PCMA")
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder("audio/x-alaw")
|
||||
.field("channels", 1i32)
|
||||
.field("rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::basedepay::RtpBaseDepay2Impl for RtpPcmaDepay {}
|
||||
|
||||
impl super::RtpPcmauDepayImpl for RtpPcmaDepay {}
|
||||
|
||||
/**
|
||||
* SECTION:element-rtppcmudepay2
|
||||
* @see_also: rtppcmupay2, rtppcmapay2, rtppcmadepay2, mulawenc, mulawdec
|
||||
*
|
||||
* Extracts µ-law encoded audio from RTP packets as per [RFC 3551][rfc-3551].
|
||||
*
|
||||
* [rfc-3551]: https://www.rfc-editor.org/rfc/rfc3551.html#section-4.5.14
|
||||
*
|
||||
* ## Example pipeline
|
||||
*
|
||||
* |[
|
||||
* gst-launch-1.0 udpsrc caps='application/x-rtp, media=audio, clock-rate=8000, payload=0' ! rtpjitterbuffer latency=50 ! rtppcmudepay2 ! mulawdec ! audioconvert ! audioresample ! autoaudiosink
|
||||
* ]| This will depayload an incoming RTP µ-law audio stream. You can use the #rtppcmupay2 and
|
||||
* mulawenc elements to create such an RTP stream.
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmuDepay;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmuDepay {
|
||||
const NAME: &'static str = "GstRtpPcmuDepay2";
|
||||
type Type = super::RtpPcmuDepay;
|
||||
type ParentType = super::RtpPcmauDepay;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmuDepay {}
|
||||
|
||||
impl GstObjectImpl for RtpPcmuDepay {}
|
||||
|
||||
impl ElementImpl for RtpPcmuDepay {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"RTP PCMU Depayloader",
|
||||
"Codec/Depayloader/Network/RTP",
|
||||
"Depayload µ-law from RTP packets (RFC 3551)",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder_full()
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", 0i32)
|
||||
.field("clock-rate", 8000i32)
|
||||
.build(),
|
||||
)
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("clock-rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.field("encoding-name", "PCMU")
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder("audio/x-mulaw")
|
||||
.field("channels", 1i32)
|
||||
.field("rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::basedepay::RtpBaseDepay2Impl for RtpPcmuDepay {}
|
||||
|
||||
impl super::RtpPcmauDepayImpl for RtpPcmuDepay {}
|
59
net/rtp/src/pcmau/depay/mod.rs
Normal file
59
net/rtp/src/pcmau/depay/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
pub mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmauDepay(ObjectSubclass<imp::RtpPcmauDepay>)
|
||||
@extends crate::basedepay::RtpBaseDepay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub trait RtpPcmauDepayImpl: crate::basedepay::RtpBaseDepay2Impl {}
|
||||
|
||||
unsafe impl<T: RtpPcmauDepayImpl> IsSubclassable<T> for RtpPcmauDepay {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmaDepay(ObjectSubclass<imp::RtpPcmaDepay>)
|
||||
@extends RtpPcmauDepay, crate::basedepay::RtpBaseDepay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmuDepay(ObjectSubclass<imp::RtpPcmuDepay>)
|
||||
@extends RtpPcmauDepay, crate::basedepay::RtpBaseDepay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
use gst::prelude::*;
|
||||
|
||||
// Make internal base class available in docs
|
||||
crate::pcmau::depay::RtpPcmauDepay::static_type()
|
||||
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"rtppcmadepay2",
|
||||
gst::Rank::MARGINAL,
|
||||
RtpPcmaDepay::static_type(),
|
||||
)?;
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"rtppcmudepay2",
|
||||
gst::Rank::MARGINAL,
|
||||
RtpPcmuDepay::static_type(),
|
||||
)
|
||||
}
|
14
net/rtp/src/pcmau/mod.rs
Normal file
14
net/rtp/src/pcmau/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod depay;
|
||||
pub mod pay;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
294
net/rtp/src/pcmau/pay/imp.rs
Normal file
294
net/rtp/src/pcmau/pay/imp.rs
Normal file
|
@ -0,0 +1,294 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
baseaudiopay::RtpBaseAudioPay2Ext,
|
||||
basepay::{RtpBasePay2Ext, RtpBasePay2ImplExt},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmauPay;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmauPay {
|
||||
const ABSTRACT: bool = true;
|
||||
const NAME: &'static str = "GstRtpPcmauPay2";
|
||||
type Type = super::RtpPcmauPay;
|
||||
type ParentType = crate::baseaudiopay::RtpBaseAudioPay2;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmauPay {}
|
||||
|
||||
impl GstObjectImpl for RtpPcmauPay {}
|
||||
|
||||
impl ElementImpl for RtpPcmauPay {}
|
||||
|
||||
impl crate::basepay::RtpBasePay2Impl for RtpPcmauPay {
|
||||
fn set_sink_caps(&self, caps: &gst::Caps) -> bool {
|
||||
let s = caps.structure(0).unwrap();
|
||||
|
||||
let rate = u32::try_from(s.get::<i32>("rate").unwrap()).unwrap();
|
||||
|
||||
let src_caps = gst::Caps::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field(
|
||||
"encoding-name",
|
||||
if self.obj().type_() == super::RtpPcmaPay::static_type() {
|
||||
"PCMA"
|
||||
} else {
|
||||
"PCMU"
|
||||
},
|
||||
)
|
||||
.field("clock-rate", rate as i32)
|
||||
.build();
|
||||
|
||||
self.obj().set_src_caps(&src_caps);
|
||||
self.obj().set_bpf(1);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
fn sink_query(&self, query: &mut gst::QueryRef) -> bool {
|
||||
match query.view_mut() {
|
||||
gst::QueryViewMut::Caps(query) => {
|
||||
let mut caps = self.obj().sink_pad().pad_template_caps();
|
||||
|
||||
// If the payload type is 0 or 8 then only 8000Hz are supported.
|
||||
if [0, 8].contains(&self.obj().property::<u32>("pt")) {
|
||||
let caps = caps.make_mut();
|
||||
caps.set("rate", 8000);
|
||||
}
|
||||
|
||||
if let Some(filter) = query.filter() {
|
||||
caps = filter.intersect_with_mode(&caps, gst::CapsIntersectMode::First);
|
||||
}
|
||||
|
||||
query.set_result(&caps);
|
||||
|
||||
return true;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_sink_query(query)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::baseaudiopay::RtpBaseAudioPay2Impl for RtpPcmauPay {}
|
||||
|
||||
/**
|
||||
* SECTION:element-rtppcmapay2
|
||||
* @see_also: rtppcmadepay2, rtppcmupay2, rtppcmudepay2, alawenc, alawdec
|
||||
*
|
||||
* Payloads A-law encoded audio into RTP packets as per [RFC 3551][rfc-3551].
|
||||
*
|
||||
* [rfc-3551]: https://www.rfc-editor.org/rfc/rfc3551.html#section-4.5.10
|
||||
*
|
||||
* ## Example pipeline
|
||||
*
|
||||
* |[
|
||||
* gst-launch-1.0 audiotestsrc wave=ticks ! audio/x-raw,rate=8000,channels=1 ! alawenc ! rtppcmapay2 ! udpsink host=127.0.0.1 port=5004
|
||||
* ]| This will generate an A-law audio test signal and payload it as RTP and send it out
|
||||
* as UDP to localhost port 5004.
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmaPay;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmaPay {
|
||||
const NAME: &'static str = "GstRtpPcmaPay2";
|
||||
type Type = super::RtpPcmaPay;
|
||||
type ParentType = super::RtpPcmauPay;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmaPay {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
// Default to payload type 8
|
||||
self.obj().set_property("pt", 8u32);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for RtpPcmaPay {}
|
||||
|
||||
impl ElementImpl for RtpPcmaPay {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"RTP PCMA Payloader",
|
||||
"Codec/Payloader/Network/RTP",
|
||||
"Payload A-law Audio into RTP packets (RFC 3551)",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder("audio/x-alaw")
|
||||
.field("channels", 1i32)
|
||||
.field("rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder_full()
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", 8i32)
|
||||
.field("clock-rate", 8000i32)
|
||||
.build(),
|
||||
)
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", gst::IntRange::new(96i32, 127i32))
|
||||
.field("encoding-name", "PCMA")
|
||||
.field("clock-rate", gst::IntRange::new(1, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::basepay::RtpBasePay2Impl for RtpPcmaPay {}
|
||||
|
||||
impl crate::baseaudiopay::RtpBaseAudioPay2Impl for RtpPcmaPay {}
|
||||
|
||||
impl super::RtpPcmauPayImpl for RtpPcmaPay {}
|
||||
|
||||
/**
|
||||
* SECTION:element-rtppcmupay2
|
||||
* @see_also: rtppcmudepay2, rtppcmapay2, rtppcmadepay2, mulawenc, mulawdec
|
||||
*
|
||||
* Payloads µ-law encoded audio into RTP packets as per [RFC 3551][rfc-3551].
|
||||
*
|
||||
* [rfc-3551]: https://www.rfc-editor.org/rfc/rfc3551.html#section-4.5.10
|
||||
*
|
||||
* ## Example pipeline
|
||||
*
|
||||
* |[
|
||||
* gst-launch-1.0 audiotestsrc wave=ticks ! audio/x-raw,rate=8000,channels=1 ! mulawenc ! rtppcmupay2 ! udpsink host=127.0.0.1 port=5004
|
||||
* ]| This will generate a µ-law audio test signal and payload it as RTP and send it out
|
||||
* as UDP to localhost port 5004.
|
||||
*
|
||||
* Since: plugins-rs-0.13.0
|
||||
*/
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RtpPcmuPay;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RtpPcmuPay {
|
||||
const NAME: &'static str = "GstRtpPcmuPay2";
|
||||
type Type = super::RtpPcmuPay;
|
||||
type ParentType = super::RtpPcmauPay;
|
||||
}
|
||||
|
||||
impl ObjectImpl for RtpPcmuPay {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
// Default to payload type 0
|
||||
self.obj().set_property("pt", 0u32);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for RtpPcmuPay {}
|
||||
|
||||
impl ElementImpl for RtpPcmuPay {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"RTP PCMU Payloader",
|
||||
"Codec/Payloader/Network/RTP",
|
||||
"Payload µ-law Audio into RTP packets (RFC 3551)",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder("audio/x-mulaw")
|
||||
.field("channels", 1i32)
|
||||
.field("rate", gst::IntRange::new(1i32, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&gst::Caps::builder_full()
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", 0i32)
|
||||
.field("clock-rate", 8000i32)
|
||||
.build(),
|
||||
)
|
||||
.structure(
|
||||
gst::Structure::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", gst::IntRange::new(96i32, 127i32))
|
||||
.field("encoding-name", "PCMU")
|
||||
.field("clock-rate", gst::IntRange::new(1, i32::MAX))
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template, sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::basepay::RtpBasePay2Impl for RtpPcmuPay {}
|
||||
|
||||
impl crate::baseaudiopay::RtpBaseAudioPay2Impl for RtpPcmuPay {}
|
||||
|
||||
impl super::RtpPcmauPayImpl for RtpPcmuPay {}
|
59
net/rtp/src/pcmau/pay/mod.rs
Normal file
59
net/rtp/src/pcmau/pay/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::{glib, prelude::*, subclass::prelude::*};
|
||||
|
||||
pub mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmauPay(ObjectSubclass<imp::RtpPcmauPay>)
|
||||
@extends crate::baseaudiopay::RtpBaseAudioPay2, crate::basepay::RtpBasePay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub trait RtpPcmauPayImpl: crate::baseaudiopay::RtpBaseAudioPay2Impl {}
|
||||
|
||||
unsafe impl<T: RtpPcmauPayImpl> IsSubclassable<T> for RtpPcmauPay {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
Self::parent_class_init::<T>(class);
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmaPay(ObjectSubclass<imp::RtpPcmaPay>)
|
||||
@extends RtpPcmauPay, crate::baseaudiopay::RtpBaseAudioPay2, crate::basepay::RtpBasePay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RtpPcmuPay(ObjectSubclass<imp::RtpPcmuPay>)
|
||||
@extends RtpPcmauPay, crate::baseaudiopay::RtpBaseAudioPay2, crate::basepay::RtpBasePay2, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
use gst::prelude::*;
|
||||
|
||||
// Make internal base class available in docs
|
||||
crate::pcmau::pay::RtpPcmauPay::static_type()
|
||||
.mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"rtppcmapay2",
|
||||
gst::Rank::MARGINAL,
|
||||
RtpPcmaPay::static_type(),
|
||||
)?;
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"rtppcmupay2",
|
||||
gst::Rank::MARGINAL,
|
||||
RtpPcmuPay::static_type(),
|
||||
)
|
||||
}
|
239
net/rtp/src/pcmau/tests.rs
Normal file
239
net/rtp/src/pcmau/tests.rs
Normal file
|
@ -0,0 +1,239 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::tests::{run_test_pipeline, ExpectedBuffer, ExpectedPacket, Source};
|
||||
|
||||
use std::cmp;
|
||||
|
||||
fn init() {
|
||||
use std::sync::Once;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| {
|
||||
gst::init().unwrap();
|
||||
crate::plugin_register_static().expect("rtppcmau test");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pcma() {
|
||||
init();
|
||||
|
||||
let src = "audiotestsrc num-buffers=100 samplesperbuffer=400 ! audio/x-raw,rate=8000,channels=1 ! alawenc";
|
||||
let pay = "rtppcmapay2";
|
||||
let depay = "rtppcmadepay2";
|
||||
|
||||
let mut expected_pay = Vec::with_capacity(100);
|
||||
for i in 0..100 {
|
||||
expected_pay.push(vec![ExpectedPacket::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(i * 50))
|
||||
.flags(if i == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::MARKER
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.pt(8)
|
||||
.rtp_time(((i * 400) & 0xffff_ffff) as u32)
|
||||
.marker_bit(i == 0)
|
||||
.build()]);
|
||||
}
|
||||
|
||||
let mut expected_depay = Vec::with_capacity(100);
|
||||
for i in 0..100 {
|
||||
expected_depay.push(vec![ExpectedBuffer::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(i * 50))
|
||||
.size(400)
|
||||
.flags(if i == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::RESYNC
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.build()]);
|
||||
}
|
||||
|
||||
run_test_pipeline(Source::Bin(src), pay, depay, expected_pay, expected_depay);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pcma_splitting() {
|
||||
init();
|
||||
|
||||
let src = "audiotestsrc num-buffers=100 samplesperbuffer=480 ! audio/x-raw,rate=8000,channels=1 ! alawenc";
|
||||
let pay = "rtppcmapay2 min-ptime=25000000 max-ptime=50000000";
|
||||
let depay = "rtppcmadepay2";
|
||||
|
||||
// Every input buffer is 480 samples, every packet can contain between 200 and 400 samples
|
||||
// so a bit of splitting is necessary and sometimes the remaining queued data ends up filling
|
||||
// one additional packet
|
||||
let mut expected_pay = Vec::with_capacity(134);
|
||||
let mut queued = 0;
|
||||
let mut pos = 0;
|
||||
for i in 0..134 {
|
||||
if i < 100 {
|
||||
queued += 480;
|
||||
}
|
||||
|
||||
while (i < 100 && queued >= 200) || (i == 100 && queued > 0) {
|
||||
let size = cmp::min(queued, 400);
|
||||
queued -= size;
|
||||
|
||||
expected_pay.push(vec![ExpectedPacket::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(pos / 8))
|
||||
.size(size as usize + 12)
|
||||
.flags(if pos == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::MARKER
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.pt(8)
|
||||
.rtp_time((pos & 0xffff_ffff) as u32)
|
||||
.marker_bit(pos == 0)
|
||||
.build()]);
|
||||
|
||||
pos += size;
|
||||
}
|
||||
}
|
||||
|
||||
let mut expected_depay = Vec::with_capacity(134);
|
||||
for packets in &expected_pay {
|
||||
for packet in packets {
|
||||
expected_depay.push(vec![ExpectedBuffer::builder()
|
||||
.pts(packet.pts)
|
||||
.maybe_size(packet.size.map(|size| size - 12))
|
||||
.flags(if packet.pts.is_zero() {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::RESYNC
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.build()]);
|
||||
}
|
||||
}
|
||||
|
||||
run_test_pipeline(Source::Bin(src), pay, depay, expected_pay, expected_depay);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pcma_discont() {
|
||||
init();
|
||||
|
||||
let caps = gst::Caps::builder("audio/x-alaw")
|
||||
.field("channels", 1)
|
||||
.field("rate", 8000i32)
|
||||
.build();
|
||||
|
||||
let mut buffers = Vec::with_capacity(10);
|
||||
let mut pos = 0;
|
||||
// First 5 buffers are normal, then a 10s jump
|
||||
for _ in 0..10 {
|
||||
let mut buffer = gst::Buffer::with_size(400).unwrap();
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(gst::ClockTime::from_mseconds(pos / 8));
|
||||
}
|
||||
|
||||
buffers.push(buffer);
|
||||
|
||||
pos += 400;
|
||||
if pos == 2000 {
|
||||
pos += 80000;
|
||||
}
|
||||
}
|
||||
|
||||
let pay = "rtppcmapay2 discont-wait=25000000";
|
||||
let depay = "rtppcmadepay2";
|
||||
|
||||
let mut expected_pay = Vec::with_capacity(10);
|
||||
let mut pos = 0;
|
||||
for _ in 0..10 {
|
||||
expected_pay.push(vec![ExpectedPacket::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(pos / 8))
|
||||
.size(412)
|
||||
.flags(if pos == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::MARKER
|
||||
} else if pos == 82000 {
|
||||
gst::BufferFlags::MARKER
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.pt(8)
|
||||
.rtp_time((pos & 0xffff_ffff) as u32)
|
||||
.marker_bit(pos == 0 || pos == 82000)
|
||||
.build()]);
|
||||
|
||||
pos += 400;
|
||||
if pos == 2000 {
|
||||
pos += 80000;
|
||||
}
|
||||
}
|
||||
|
||||
let mut expected_depay = Vec::with_capacity(10);
|
||||
for packets in &expected_pay {
|
||||
for packet in packets {
|
||||
expected_depay.push(vec![ExpectedBuffer::builder()
|
||||
.pts(packet.pts)
|
||||
.maybe_size(packet.size.map(|size| size - 12))
|
||||
.flags(if packet.pts.is_zero() {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::RESYNC
|
||||
} else if packet.flags.contains(gst::BufferFlags::MARKER) {
|
||||
gst::BufferFlags::RESYNC
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.build()]);
|
||||
}
|
||||
}
|
||||
|
||||
run_test_pipeline(
|
||||
Source::Buffers(caps, buffers),
|
||||
pay,
|
||||
depay,
|
||||
expected_pay,
|
||||
expected_depay,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pcmu() {
|
||||
init();
|
||||
|
||||
let src = "audiotestsrc num-buffers=100 samplesperbuffer=400 ! audio/x-raw,rate=8000,channels=1 ! mulawenc";
|
||||
let pay = "rtppcmupay2";
|
||||
let depay = "rtppcmudepay2";
|
||||
|
||||
let mut expected_pay = Vec::with_capacity(100);
|
||||
for i in 0..100 {
|
||||
expected_pay.push(vec![ExpectedPacket::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(i * 50))
|
||||
.size(412)
|
||||
.flags(if i == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::MARKER
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.pt(0)
|
||||
.rtp_time(((i * 400) & 0xffff_ffff) as u32)
|
||||
.marker_bit(i == 0)
|
||||
.build()]);
|
||||
}
|
||||
|
||||
let mut expected_depay = Vec::with_capacity(100);
|
||||
for i in 0..100 {
|
||||
expected_depay.push(vec![ExpectedBuffer::builder()
|
||||
.pts(gst::ClockTime::from_mseconds(i * 50))
|
||||
.size(400)
|
||||
.flags(if i == 0 {
|
||||
gst::BufferFlags::DISCONT | gst::BufferFlags::RESYNC
|
||||
} else {
|
||||
gst::BufferFlags::empty()
|
||||
})
|
||||
.build()]);
|
||||
}
|
||||
|
||||
run_test_pipeline(Source::Bin(src), pay, depay, expected_pay, expected_depay);
|
||||
}
|
515
net/rtp/src/tests.rs
Normal file
515
net/rtp/src/tests.rs
Normal file
|
@ -0,0 +1,515 @@
|
|||
//
|
||||
// Copyright (C) 2023 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
mem,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use gst::prelude::*;
|
||||
|
||||
/// Expected packet produced by the payloader
|
||||
pub struct ExpectedPacket {
|
||||
/// All packets are expected to have a known and fixed PTS.
|
||||
pub pts: gst::ClockTime,
|
||||
/// If not set the size will not be checked.
|
||||
pub size: Option<usize>,
|
||||
pub flags: gst::BufferFlags,
|
||||
pub pt: u8,
|
||||
pub rtp_time: u32,
|
||||
pub marker: bool,
|
||||
}
|
||||
|
||||
impl ExpectedPacket {
|
||||
/// Creates a builder for an `ExpectedPacket`.
|
||||
///
|
||||
/// Assigns the following packet default values:
|
||||
///
|
||||
/// * pts: gst::ClockTime::ZERO
|
||||
/// * size: None => not checked
|
||||
/// * flags: gst::BufferFlags::empty()
|
||||
/// * pt: 96
|
||||
/// * rtp_time: 0
|
||||
/// * marker: true
|
||||
pub fn builder() -> ExpectedPacketBuilder {
|
||||
ExpectedPacketBuilder(ExpectedPacket {
|
||||
pts: gst::ClockTime::ZERO,
|
||||
size: None,
|
||||
flags: gst::BufferFlags::empty(),
|
||||
pt: 96,
|
||||
rtp_time: 0,
|
||||
marker: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpectedPacketBuilder(ExpectedPacket);
|
||||
impl ExpectedPacketBuilder {
|
||||
pub fn pts(mut self, pts: gst::ClockTime) -> Self {
|
||||
self.0.pts = pts;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: usize) -> Self {
|
||||
self.0.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn flags(mut self, flags: gst::BufferFlags) -> Self {
|
||||
self.0.flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pt(mut self, pt: u8) -> Self {
|
||||
self.0.pt = pt;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rtp_time(mut self, rtp_time: u32) -> Self {
|
||||
self.0.rtp_time = rtp_time;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn marker_bit(mut self, marker: bool) -> Self {
|
||||
self.0.marker = marker;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ExpectedPacket {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Expected buffer produced by the depayloader
|
||||
#[derive(Debug)]
|
||||
pub struct ExpectedBuffer {
|
||||
/// If not set then it is asserted that the depayloaded buffer also has no PTS.
|
||||
pub pts: Option<gst::ClockTime>,
|
||||
/// If not set then it is asserted that the depayloaded buffer also has no DTS.
|
||||
pub dts: Option<gst::ClockTime>,
|
||||
/// If not set the size will not be checked.
|
||||
pub size: Option<usize>,
|
||||
pub flags: gst::BufferFlags,
|
||||
}
|
||||
|
||||
impl ExpectedBuffer {
|
||||
/// Creates a builder for an `ExpectedBuffer`.
|
||||
///
|
||||
/// Assigns the following buffer default values:
|
||||
///
|
||||
/// * pts: None
|
||||
/// * dts: None
|
||||
/// * size: None => not checked
|
||||
/// * flags: gst::BufferFlags::empty()
|
||||
pub fn builder() -> ExpectedBufferBuilder {
|
||||
ExpectedBufferBuilder(ExpectedBuffer {
|
||||
pts: None,
|
||||
dts: None,
|
||||
size: None,
|
||||
flags: gst::BufferFlags::empty(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExpectedBufferBuilder(ExpectedBuffer);
|
||||
#[allow(dead_code)]
|
||||
impl ExpectedBufferBuilder {
|
||||
pub fn pts(mut self, pts: gst::ClockTime) -> Self {
|
||||
self.0.pts = Some(pts);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_pts(mut self, pts: Option<gst::ClockTime>) -> Self {
|
||||
self.0.pts = pts;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn dts(mut self, dts: gst::ClockTime) -> Self {
|
||||
self.0.dts = Some(dts);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn size(mut self, size: usize) -> Self {
|
||||
self.0.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_size(mut self, size: Option<usize>) -> Self {
|
||||
self.0.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn flags(mut self, flags: gst::BufferFlags) -> Self {
|
||||
self.0.flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ExpectedBuffer {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the test
|
||||
pub enum Source<'a> {
|
||||
#[allow(dead_code)]
|
||||
Buffers(gst::Caps, Vec<gst::Buffer>),
|
||||
Bin(&'a str),
|
||||
}
|
||||
|
||||
/// Pipeline wrapper to automatically set state to `Null` on drop
|
||||
///
|
||||
/// Useful to not get critical warnings on panics from unwinding.
|
||||
struct Pipeline(gst::Pipeline);
|
||||
|
||||
impl Drop for Pipeline {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.set_state(gst::State::Null);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Pipeline {
|
||||
type Target = gst::Pipeline;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test_pipeline(
|
||||
src: Source,
|
||||
pay: &str,
|
||||
depay: &str,
|
||||
expected_pay: Vec<Vec<ExpectedPacket>>,
|
||||
expected_depay: Vec<Vec<ExpectedBuffer>>,
|
||||
) {
|
||||
let pipeline = Pipeline(gst::Pipeline::new());
|
||||
|
||||
// Return if the pipelines can't be built: this likely means that encoders are missing
|
||||
|
||||
let src = match src {
|
||||
Source::Bin(src) => {
|
||||
let Ok(src) = gst::parse::bin_from_description_with_name(src, true, "rtptestsrc")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
src.upcast::<gst::Element>()
|
||||
}
|
||||
Source::Buffers(caps, buffers) => {
|
||||
let mut buffers = buffers.into_iter();
|
||||
let appsrc = gst_app::AppSrc::builder()
|
||||
.format(gst::Format::Time)
|
||||
.caps(&caps)
|
||||
.callbacks(
|
||||
gst_app::AppSrcCallbacks::builder()
|
||||
.need_data(move |appsrc, _offset| {
|
||||
let Some(buffer) = buffers.next() else {
|
||||
let _ = appsrc.end_of_stream();
|
||||
return;
|
||||
};
|
||||
|
||||
// appsrc already handles the error for us
|
||||
let _ = appsrc.push_buffer(buffer);
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
appsrc.upcast::<gst::Element>()
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(pay) = gst::parse::bin_from_description_with_name(pay, true, "rtptestpay") else {
|
||||
return;
|
||||
};
|
||||
let pay = pay.upcast::<gst::Element>();
|
||||
|
||||
// Collect samples from after the payloader
|
||||
let pay_samples = Arc::new(Mutex::new(Vec::new()));
|
||||
pay.static_pad("src")
|
||||
.unwrap()
|
||||
.add_probe(
|
||||
gst::PadProbeType::BUFFER | gst::PadProbeType::BUFFER_LIST,
|
||||
{
|
||||
let pay_samples = pay_samples.clone();
|
||||
move |pad, info| {
|
||||
let segment_event = pad.sticky_event::<gst::event::Segment>(0).unwrap();
|
||||
let segment = segment_event.segment().clone();
|
||||
|
||||
let caps = pad.current_caps().unwrap();
|
||||
|
||||
let mut sample_builder = gst::Sample::builder().segment(&segment).caps(&caps);
|
||||
if let Some(buffer) = info.buffer() {
|
||||
sample_builder = sample_builder.buffer(buffer);
|
||||
} else if let Some(list) = info.buffer_list() {
|
||||
sample_builder = sample_builder.buffer_list(list);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pay_samples.lock().unwrap().push(sample_builder.build());
|
||||
|
||||
gst::PadProbeReturn::Ok
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Ok(depay) = gst::parse::bin_from_description_with_name(depay, true, "rtptestdepay") else {
|
||||
return;
|
||||
};
|
||||
let depay = depay.upcast::<gst::Element>();
|
||||
|
||||
let depay_samples = Arc::new(Mutex::new(Vec::new()));
|
||||
let appsink = gst_app::AppSink::builder()
|
||||
.sync(false)
|
||||
.buffer_list(true)
|
||||
.callbacks(
|
||||
gst_app::AppSinkCallbacks::builder()
|
||||
.new_sample({
|
||||
let depay_samples = depay_samples.clone();
|
||||
move |appsink| {
|
||||
let Ok(sample) = appsink.pull_sample() else {
|
||||
return Err(gst::FlowError::Flushing);
|
||||
};
|
||||
|
||||
depay_samples.lock().unwrap().push(sample);
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
pipeline
|
||||
.add_many([&src, &pay, &depay, appsink.as_ref()])
|
||||
.unwrap();
|
||||
gst::Element::link_many([&src, &pay, &depay, appsink.as_ref()]).unwrap();
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Playing)
|
||||
.expect("Failed to set pipeline to Playing");
|
||||
|
||||
let msg = pipeline
|
||||
.bus()
|
||||
.unwrap()
|
||||
.timed_pop_filtered(
|
||||
gst::ClockTime::NONE,
|
||||
&[gst::MessageType::Error, gst::MessageType::Eos],
|
||||
)
|
||||
.expect("Didn't receive ERROR or EOS message");
|
||||
|
||||
assert_ne!(
|
||||
msg.type_(),
|
||||
gst::MessageType::Error,
|
||||
"Received error message {msg:?}"
|
||||
);
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Null)
|
||||
.expect("Failed to set pipeline to Null");
|
||||
|
||||
drop(msg);
|
||||
drop(src);
|
||||
drop(pay);
|
||||
drop(depay);
|
||||
drop(appsink);
|
||||
drop(pipeline);
|
||||
|
||||
let pay_samples = mem::take(&mut *pay_samples.lock().unwrap());
|
||||
let depay_samples = mem::take(&mut *depay_samples.lock().unwrap());
|
||||
|
||||
// Now check against the expected values
|
||||
|
||||
assert_eq!(
|
||||
pay_samples.len(),
|
||||
expected_pay.len(),
|
||||
"Expected {} payload packets but got {}",
|
||||
expected_pay.len(),
|
||||
pay_samples.len()
|
||||
);
|
||||
|
||||
let mut initial_timestamp = None;
|
||||
|
||||
for (i, (expected_list, sample)) in
|
||||
Iterator::zip(expected_pay.into_iter(), pay_samples.into_iter()).enumerate()
|
||||
{
|
||||
let mut iter_a;
|
||||
let mut iter_b;
|
||||
|
||||
let buffer_iter: &mut dyn Iterator<Item = &gst::BufferRef> =
|
||||
if let Some(list) = sample.buffer_list() {
|
||||
assert_eq!(
|
||||
list.len(),
|
||||
expected_list.len(),
|
||||
"Expected {} buffers in {}-th payload list but got {}",
|
||||
expected_list.len(),
|
||||
i,
|
||||
list.len()
|
||||
);
|
||||
iter_a = list.iter();
|
||||
&mut iter_a
|
||||
} else {
|
||||
let buffer = sample.buffer().unwrap();
|
||||
assert_eq!(
|
||||
expected_list.len(),
|
||||
1,
|
||||
"Expected {} buffers in {}-th payload list but got 1",
|
||||
expected_list.len(),
|
||||
i,
|
||||
);
|
||||
iter_b = Some(buffer).into_iter();
|
||||
&mut iter_b
|
||||
};
|
||||
|
||||
for (j, (expected_buffer, buffer)) in
|
||||
Iterator::zip(expected_list.into_iter(), buffer_iter).enumerate()
|
||||
{
|
||||
let buffer_pts = buffer.pts().expect("Buffer without PTS");
|
||||
assert_eq!(
|
||||
buffer_pts, expected_buffer.pts,
|
||||
"Buffer {} of payload buffer list {} has unexpected PTS {} instead of {}",
|
||||
j, i, buffer_pts, expected_buffer.pts,
|
||||
);
|
||||
|
||||
if let Some(expected_size) = expected_buffer.size {
|
||||
assert_eq!(
|
||||
buffer.size(),
|
||||
expected_size,
|
||||
"Buffer {} of payload buffer list {} has unexpected size {} instead of {}",
|
||||
j,
|
||||
i,
|
||||
buffer.size(),
|
||||
expected_size,
|
||||
);
|
||||
}
|
||||
|
||||
let buffer_flags = buffer.flags() - gst::BufferFlags::TAG_MEMORY;
|
||||
assert_eq!(
|
||||
buffer_flags, expected_buffer.flags,
|
||||
"Buffer {} of payload buffer list {} has unexpected flags {:?} instead of {:?}",
|
||||
j, i, buffer_flags, expected_buffer.flags,
|
||||
);
|
||||
|
||||
let map = buffer.map_readable().unwrap();
|
||||
let rtp_packet = rtp_types::RtpPacket::parse(&map).expect("Invalid RTP packet");
|
||||
assert_eq!(
|
||||
rtp_packet.payload_type(),
|
||||
expected_buffer.pt,
|
||||
"Buffer {} of payload buffer list {} has unexpected payload type {:?} instead of {:?}",
|
||||
j,
|
||||
i,
|
||||
rtp_packet.payload_type(),
|
||||
expected_buffer.pt,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
rtp_packet.marker_bit(),
|
||||
expected_buffer.marker,
|
||||
"Buffer {} of payload buffer list {} has unexpected marker {:?} instead of {:?}",
|
||||
j,
|
||||
i,
|
||||
rtp_packet.marker_bit(),
|
||||
expected_buffer.marker,
|
||||
);
|
||||
|
||||
if initial_timestamp.is_none() {
|
||||
initial_timestamp = Some(rtp_packet.timestamp());
|
||||
}
|
||||
let initial_timestamp = initial_timestamp.unwrap();
|
||||
|
||||
let expected_timestamp = expected_buffer.rtp_time.wrapping_add(initial_timestamp);
|
||||
|
||||
assert_eq!(
|
||||
rtp_packet.timestamp(),
|
||||
expected_timestamp,
|
||||
"Buffer {} of payload buffer list {} has unexpected RTP timestamp {:?} instead of {:?}",
|
||||
j,
|
||||
i,
|
||||
rtp_packet.timestamp(),
|
||||
expected_timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
depay_samples.len(),
|
||||
expected_depay.len(),
|
||||
"Expected {} depayload samples but got {}",
|
||||
expected_depay.len(),
|
||||
depay_samples.len()
|
||||
);
|
||||
|
||||
for (i, (expected_list, sample)) in
|
||||
Iterator::zip(expected_depay.into_iter(), depay_samples.into_iter()).enumerate()
|
||||
{
|
||||
let mut iter_a;
|
||||
let mut iter_b;
|
||||
|
||||
let buffer_iter: &mut dyn Iterator<Item = &gst::BufferRef> =
|
||||
if let Some(list) = sample.buffer_list() {
|
||||
assert_eq!(
|
||||
list.len(),
|
||||
expected_list.len(),
|
||||
"Expected {} depayload buffers in {}-th list but got {}",
|
||||
expected_list.len(),
|
||||
i,
|
||||
list.len()
|
||||
);
|
||||
iter_a = list.iter();
|
||||
&mut iter_a
|
||||
} else {
|
||||
let buffer = sample.buffer().unwrap();
|
||||
assert_eq!(
|
||||
expected_list.len(),
|
||||
1,
|
||||
"Expected {} depayload buffers in {}-th list but got 1",
|
||||
expected_list.len(),
|
||||
i,
|
||||
);
|
||||
iter_b = Some(buffer).into_iter();
|
||||
&mut iter_b
|
||||
};
|
||||
|
||||
for (j, (expected_buffer, buffer)) in
|
||||
Iterator::zip(expected_list.into_iter(), buffer_iter).enumerate()
|
||||
{
|
||||
let buffer_pts = buffer.pts();
|
||||
assert_eq!(
|
||||
buffer_pts,
|
||||
expected_buffer.pts,
|
||||
"Buffer {} of depayload buffer list {} has unexpected PTS {} instead of {}",
|
||||
j,
|
||||
i,
|
||||
buffer_pts.display(),
|
||||
expected_buffer.pts.display(),
|
||||
);
|
||||
|
||||
if let Some(expected_size) = expected_buffer.size {
|
||||
assert_eq!(
|
||||
buffer.size(),
|
||||
expected_size,
|
||||
"Buffer {} of depayload buffer list {} has unexpected size {} instead of {}",
|
||||
j,
|
||||
i,
|
||||
buffer.size(),
|
||||
expected_size,
|
||||
);
|
||||
}
|
||||
|
||||
let buffer_flags = buffer.flags() - gst::BufferFlags::TAG_MEMORY;
|
||||
assert_eq!(
|
||||
buffer_flags, expected_buffer.flags,
|
||||
"Buffer {} of depayload buffer list {} has unexpected flags {:?} instead of {:?}",
|
||||
j, i, buffer_flags, expected_buffer.flags,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue