Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArun Raghavan <arun@asymptotic.io>2020-04-04 02:26:41 +0300
committerArun Raghavan <arun@arunraghavan.net>2020-04-05 22:10:46 +0300
commit205b6040fbb918c0fa736874b09f8e3f3f261e44 (patch)
tree37ed0d22ece764ec91dccefd3c2148dc26d5b96b /generic
parent5d992692f0c494c64a379de6201239efe614a15e (diff)
Reorganise plugins into directories by function
This should start making navigating the tree a little easier to start with, and we can then move to allowing building specific groups of plugins as well. The plugins are moved into the following hierarchy: audio / gst-plugin-audiofx / gst-plugin-claxon / gst-plugin-csound / gst-plugin-lewton generic / gst-plugin-file / gst-plugin-sodium / gst-plugin-threadshare net / gst-plugin-reqwest / gst-plugin-rusoto utils / gst-plugin-fallbackswitch / gst-plugin-togglerecord video / gst-plugin-cdg / gst-plugin-closedcaption / gst-plugin-dav1d / gst-plugin-flv / gst-plugin-gif / gst-plugin-rav1e gst-plugin-tutorial gst-plugin-version-helper
Diffstat (limited to 'generic')
-rw-r--r--generic/gst-plugin-file/Cargo.toml22
-rw-r--r--generic/gst-plugin-file/build.rs5
-rw-r--r--generic/gst-plugin-file/src/file_location.rs141
-rw-r--r--generic/gst-plugin-file/src/filesink.rs339
-rw-r--r--generic/gst-plugin-file/src/filesrc.rs393
-rw-r--r--generic/gst-plugin-file/src/lib.rs40
-rw-r--r--generic/gst-plugin-sodium/Cargo.toml57
-rw-r--r--generic/gst-plugin-sodium/build.rs5
-rw-r--r--generic/gst-plugin-sodium/examples/decrypt_example.rs150
-rw-r--r--generic/gst-plugin-sodium/examples/encrypt_example.rs145
-rw-r--r--generic/gst-plugin-sodium/examples/generate_keys.rs102
-rw-r--r--generic/gst-plugin-sodium/examples/receiver_sample.json2
-rw-r--r--generic/gst-plugin-sodium/examples/sender_sample.json1
-rw-r--r--generic/gst-plugin-sodium/src/decrypter.rs733
-rw-r--r--generic/gst-plugin-sodium/src/encrypter.rs572
-rw-r--r--generic/gst-plugin-sodium/src/lib.rs83
-rw-r--r--generic/gst-plugin-sodium/tests/decrypter.rs323
-rw-r--r--generic/gst-plugin-sodium/tests/encrypted_sample.encbin0 -> 6043 bytes
-rw-r--r--generic/gst-plugin-sodium/tests/encrypter.rs152
-rw-r--r--generic/gst-plugin-sodium/tests/sample.mp3bin0 -> 5907 bytes
-rw-r--r--generic/gst-plugin-threadshare/Cargo.toml53
-rw-r--r--generic/gst-plugin-threadshare/build.rs35
-rw-r--r--generic/gst-plugin-threadshare/examples/benchmark.rs173
-rw-r--r--generic/gst-plugin-threadshare/examples/tcpclientsrc_benchmark_sender.rs48
-rw-r--r--generic/gst-plugin-threadshare/examples/udpsrc_benchmark_sender.rs51
-rw-r--r--generic/gst-plugin-threadshare/src/appsrc.rs794
-rw-r--r--generic/gst-plugin-threadshare/src/dataqueue.rs282
-rw-r--r--generic/gst-plugin-threadshare/src/inputselector.rs684
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/jitterbuffer.rs1584
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/mod.rs458
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.c1409
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.h201
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.c430
-rw-r--r--generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.h267
-rw-r--r--generic/gst-plugin-threadshare/src/lib.rs117
-rw-r--r--generic/gst-plugin-threadshare/src/proxy.rs1332
-rw-r--r--generic/gst-plugin-threadshare/src/queue.rs867
-rw-r--r--generic/gst-plugin-threadshare/src/runtime/executor.rs853
-rw-r--r--generic/gst-plugin-threadshare/src/runtime/mod.rs70
-rw-r--r--generic/gst-plugin-threadshare/src/runtime/pad.rs1130
-rw-r--r--generic/gst-plugin-threadshare/src/runtime/task.rs725
-rw-r--r--generic/gst-plugin-threadshare/src/runtime/time.rs37
-rw-r--r--generic/gst-plugin-threadshare/src/socket.rs489
-rw-r--r--generic/gst-plugin-threadshare/src/tcpclientsrc.rs768
-rw-r--r--generic/gst-plugin-threadshare/src/udpsink.rs1521
-rw-r--r--generic/gst-plugin-threadshare/src/udpsrc.rs1002
-rw-r--r--generic/gst-plugin-threadshare/tests/appsrc.rs303
-rw-r--r--generic/gst-plugin-threadshare/tests/inputselector.rs108
-rw-r--r--generic/gst-plugin-threadshare/tests/jitterbuffer.rs172
-rw-r--r--generic/gst-plugin-threadshare/tests/pad.rs1322
-rw-r--r--generic/gst-plugin-threadshare/tests/pipeline.rs627
-rw-r--r--generic/gst-plugin-threadshare/tests/proxy.rs167
-rw-r--r--generic/gst-plugin-threadshare/tests/queue.rs100
-rw-r--r--generic/gst-plugin-threadshare/tests/tcpclientsrc.rs124
-rw-r--r--generic/gst-plugin-threadshare/tests/udpsink.rs160
-rw-r--r--generic/gst-plugin-threadshare/tests/udpsrc.rs174
56 files changed, 21902 insertions, 0 deletions
diff --git a/generic/gst-plugin-file/Cargo.toml b/generic/gst-plugin-file/Cargo.toml
new file mode 100644
index 000000000..bc1949c47
--- /dev/null
+++ b/generic/gst-plugin-file/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "gst-plugin-file"
+version = "0.6.0"
+authors = ["Sebastian Dröge <sebastian@centricular.com>"]
+repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
+license = "MIT/Apache-2.0"
+description = "Rust File Plugin"
+
+[dependencies]
+url = "2"
+glib = { git = "https://github.com/gtk-rs/glib" }
+gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+lazy_static = "1.0"
+
+[lib]
+name = "gstrsfile"
+crate-type = ["cdylib"]
+path = "src/lib.rs"
+
+[build-dependencies]
+gst-plugin-version-helper = { path="../../gst-plugin-version-helper" }
diff --git a/generic/gst-plugin-file/build.rs b/generic/gst-plugin-file/build.rs
new file mode 100644
index 000000000..0d1ddb61d
--- /dev/null
+++ b/generic/gst-plugin-file/build.rs
@@ -0,0 +1,5 @@
+extern crate gst_plugin_version_helper;
+
+fn main() {
+ gst_plugin_version_helper::get_info()
+}
diff --git a/generic/gst-plugin-file/src/file_location.rs b/generic/gst-plugin-file/src/file_location.rs
new file mode 100644
index 000000000..8eba2c01a
--- /dev/null
+++ b/generic/gst-plugin-file/src/file_location.rs
@@ -0,0 +1,141 @@
+use glib;
+use gst;
+use url::Url;
+
+use std::convert::AsRef;
+use std::fmt;
+use std::ops::Deref;
+use std::path::{Path, PathBuf};
+
+#[cfg(target_os = "windows")]
+const WIN_EXT_PATH_PREFIX: &str = "\\\\?\\";
+#[cfg(target_os = "windows")]
+const WIN_EXT_PATH_PREFIX_LEN: usize = 4;
+
+#[derive(Debug)]
+pub(super) struct FileLocation(PathBuf);
+
+impl FileLocation {
+ pub(super) fn try_from_path_str(path_str: String) -> Result<Self, glib::Error> {
+ FileLocation::try_from(PathBuf::from(path_str))
+ }
+
+ pub(super) fn try_from_uri_str(uri_str: &str) -> Result<Self, glib::Error> {
+ match Url::parse(uri_str) {
+ Ok(url) => {
+ if url.scheme() != "file" {
+ return Err(glib::Error::new(
+ gst::URIError::UnsupportedProtocol,
+ format!("Unsupported URI {}", uri_str).as_str(),
+ ));
+ }
+
+ let path = url.to_file_path().or_else(|_| {
+ Err(glib::Error::new(
+ gst::URIError::BadUri,
+ format!("Unsupported URI {}", uri_str).as_str(),
+ ))
+ })?;
+
+ FileLocation::try_from(path)
+ }
+ Err(err) => Err(glib::Error::new(
+ gst::URIError::BadUri,
+ format!("Couldn't parse URI {}: {}", uri_str, err.to_string()).as_str(),
+ )),
+ }
+ }
+
+ fn try_from(location: PathBuf) -> Result<Self, glib::Error> {
+ let location_str = location.to_str().ok_or_else(|| {
+ glib::Error::new(
+ gst::URIError::BadReference,
+ format!("Invalid path {:?}", location).as_str(),
+ )
+ })?;
+
+ let file_name = location.file_name().ok_or_else(|| {
+ glib::Error::new(
+ gst::URIError::BadReference,
+ format!("Expected a path with a filename, got {}", location_str,).as_str(),
+ )
+ })?;
+
+ // The filename might not exist yet, so check the parent only.
+ // Note: `location` contains a filename, so its parent can't be `None`
+ let mut parent_dir = location
+ .parent()
+ .expect("FileSink::set_location `location` with filename but without a parent")
+ .to_owned();
+ if parent_dir.is_relative() && parent_dir.components().next() == None {
+ // `location` only contains the filename
+ // need to specify "." for `canonicalize` to resolve the actual path
+ parent_dir = PathBuf::from(".");
+ }
+
+ let parent_canonical = parent_dir.canonicalize().map_err(|err| {
+ glib::Error::new(
+ gst::URIError::BadReference,
+ format!(
+ "Could not resolve path {}: {}",
+ location_str,
+ err.to_string(),
+ )
+ .as_str(),
+ )
+ })?;
+
+ #[cfg(target_os = "windows")]
+ let parent_canonical = {
+ let has_prefix = parent_canonical
+ .to_str()
+ .unwrap() // already checked above
+ .starts_with(WIN_EXT_PATH_PREFIX);
+ if has_prefix {
+ // Remove the "extended length path" prefix
+ // for compatibility with applications which can't deal with it.
+ // See https://doc.rust-lang.org/std/fs/fn.canonicalize.html
+ let parent_canonical_str = parent_canonical.to_str().unwrap();
+ PathBuf::from(&parent_canonical_str[WIN_EXT_PATH_PREFIX_LEN..])
+ } else {
+ parent_canonical
+ }
+ };
+
+ let location_canonical = parent_canonical.join(file_name);
+ Url::from_file_path(&location_canonical)
+ .map_err(|_| {
+ glib::Error::new(
+ gst::URIError::BadReference,
+ format!("Could not resolve path to URL {}", location_str).as_str(),
+ )
+ })
+ .map(|_| FileLocation(location_canonical))
+ }
+
+ fn to_str(&self) -> &str {
+ self.0
+ .to_str()
+ .expect("FileLocation: couldn't get `&str` from internal `PathBuf`")
+ }
+}
+
+impl AsRef<Path> for FileLocation {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+
+impl Deref for FileLocation {
+ type Target = Path;
+
+ fn deref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+
+impl fmt::Display for FileLocation {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.to_str())
+ }
+}
diff --git a/generic/gst-plugin-file/src/filesink.rs b/generic/gst-plugin-file/src/filesink.rs
new file mode 100644
index 000000000..ee912ec15
--- /dev/null
+++ b/generic/gst-plugin-file/src/filesink.rs
@@ -0,0 +1,339 @@
+// Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
+// 2016 Luis de Bethencourt <luisbg@osg.samsung.com>
+// 2018 François Laignel <fengalin@free.fr>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use glib;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst_base;
+use gst_base::subclass::prelude::*;
+
+use std::fs::File;
+use std::io::Write;
+use std::sync::Mutex;
+
+use url::Url;
+
+use file_location::FileLocation;
+
+const DEFAULT_LOCATION: Option<FileLocation> = None;
+
+#[derive(Debug)]
+struct Settings {
+ location: Option<FileLocation>,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ location: DEFAULT_LOCATION,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 1] = [subclass::Property("location", |name| {
+ glib::ParamSpec::string(
+ name,
+ "File Location",
+ "Location of the file to write",
+ None,
+ glib::ParamFlags::READWRITE,
+ )
+})];
+
+enum State {
+ Stopped,
+ Started { file: File, position: u64 },
+}
+
+impl Default for State {
+ fn default() -> State {
+ State::Stopped
+ }
+}
+
+pub struct FileSink {
+ settings: Mutex<Settings>,
+ state: Mutex<State>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "rsfilesink",
+ gst::DebugColorFlags::empty(),
+ Some("File Sink"),
+ );
+}
+
+impl FileSink {
+ fn set_location(
+ &self,
+ element: &gst_base::BaseSink,
+ location: Option<FileLocation>,
+ ) -> Result<(), glib::Error> {
+ let state = self.state.lock().unwrap();
+ if let State::Started { .. } = *state {
+ return Err(glib::Error::new(
+ gst::URIError::BadState,
+ "Changing the `location` property on a started `filesink` is not supported",
+ ));
+ }
+
+ let mut settings = self.settings.lock().unwrap();
+ settings.location = match location {
+ Some(location) => {
+ match settings.location {
+ Some(ref location_cur) => {
+ gst_info!(
+ CAT,
+ obj: element,
+ "Changing `location` from {:?} to {}",
+ location_cur,
+ location,
+ );
+ }
+ None => {
+ gst_info!(CAT, obj: element, "Setting `location` to {}", location,);
+ }
+ }
+ Some(location)
+ }
+ None => {
+ gst_info!(CAT, obj: element, "Resetting `location` to None",);
+ None
+ }
+ };
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for FileSink {
+ const NAME: &'static str = "RsFileSink";
+ type ParentType = gst_base::BaseSink;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ settings: Mutex::new(Default::default()),
+ state: Mutex::new(Default::default()),
+ }
+ }
+
+ fn type_init(type_: &mut subclass::InitializingType<Self>) {
+ type_.add_interface::<gst::URIHandler>();
+ }
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "File Sink",
+ "Sink/File",
+ "Write stream to a file",
+ "François Laignel <fengalin@free.fr>, Luis de Bethencourt <luisbg@osg.samsung.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+ }
+}
+
+impl ObjectImpl for FileSink {
+ glib_object_impl!();
+
+ fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+ match *prop {
+ subclass::Property("location", ..) => {
+ let element = obj.downcast_ref::<gst_base::BaseSink>().unwrap();
+
+ let res = match value.get::<String>() {
+ Ok(Some(location)) => FileLocation::try_from_path_str(location)
+ .and_then(|file_location| self.set_location(&element, Some(file_location))),
+ Ok(None) => self.set_location(&element, None),
+ Err(_) => unreachable!("type checked upstream"),
+ };
+
+ if let Err(err) = res {
+ gst_error!(
+ CAT,
+ obj: element,
+ "Failed to set property `location`: {}",
+ err
+ );
+ }
+ }
+ _ => unimplemented!(),
+ };
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+ match *prop {
+ subclass::Property("location", ..) => {
+ let settings = self.settings.lock().unwrap();
+ let location = settings
+ .location
+ .as_ref()
+ .map(|location| location.to_string());
+
+ Ok(location.to_value())
+ }
+ _ => unimplemented!(),
+ }
+ }
+}
+
+impl ElementImpl for FileSink {}
+
+impl BaseSinkImpl for FileSink {
+ fn start(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> {
+ let mut state = self.state.lock().unwrap();
+ if let State::Started { .. } = *state {
+ unreachable!("FileSink already started");
+ }
+
+ let settings = self.settings.lock().unwrap();
+ let location = settings.location.as_ref().ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["File location is not defined"]
+ )
+ })?;
+
+ let file = File::create(location).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ [
+ "Could not open file {} for writing: {}",
+ location,
+ err.to_string(),
+ ]
+ )
+ })?;
+ gst_debug!(CAT, obj: element, "Opened file {:?}", file);
+
+ *state = State::Started { file, position: 0 };
+ gst_info!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> {
+ let mut state = self.state.lock().unwrap();
+ if let State::Stopped = *state {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["FileSink not started"]
+ ));
+ }
+
+ *state = State::Stopped;
+ gst_info!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ // TODO: implement seek in BYTES format
+
+ fn render(
+ &self,
+ element: &gst_base::BaseSink,
+ buffer: &gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let mut state = self.state.lock().unwrap();
+ let (file, position) = match *state {
+ State::Started {
+ ref mut file,
+ ref mut position,
+ } => (file, position),
+ State::Stopped => {
+ gst_element_error!(element, gst::CoreError::Failed, ["Not started yet"]);
+ return Err(gst::FlowError::Error);
+ }
+ };
+
+ gst_trace!(CAT, obj: element, "Rendering {:?}", buffer);
+ let map = buffer.map_readable().map_err(|_| {
+ gst_element_error!(element, gst::CoreError::Failed, ["Failed to map buffer"]);
+ gst::FlowError::Error
+ })?;
+
+ file.write_all(map.as_ref()).map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::ResourceError::Write,
+ ["Failed to write buffer: {}", err]
+ );
+ gst::FlowError::Error
+ })?;
+
+ *position += map.len() as u64;
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+}
+
+impl URIHandlerImpl for FileSink {
+ fn get_uri(&self, _element: &gst::URIHandler) -> Option<String> {
+ let settings = self.settings.lock().unwrap();
+
+ // Conversion to Url already checked while building the `FileLocation`
+ settings.location.as_ref().map(|location| {
+ Url::from_file_path(location)
+ .expect("FileSink::get_uri couldn't build `Url` from `location`")
+ .into_string()
+ })
+ }
+
+ fn set_uri(&self, element: &gst::URIHandler, uri: &str) -> Result<(), glib::Error> {
+ let element = element.dynamic_cast_ref::<gst_base::BaseSink>().unwrap();
+
+ // Special case for "file://" as this is used by some applications to test
+ // with `gst_element_make_from_uri` if there's an element that supports the URI protocol
+
+ if uri != "file://" {
+ let file_location = FileLocation::try_from_uri_str(uri)?;
+ self.set_location(&element, Some(file_location))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn get_uri_type() -> gst::URIType {
+ gst::URIType::Sink
+ }
+
+ fn get_protocols() -> Vec<String> {
+ vec!["file".to_string()]
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "rsfilesink",
+ gst::Rank::None,
+ FileSink::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-file/src/filesrc.rs b/generic/gst-plugin-file/src/filesrc.rs
new file mode 100644
index 000000000..7d7b9f553
--- /dev/null
+++ b/generic/gst-plugin-file/src/filesrc.rs
@@ -0,0 +1,393 @@
+// Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
+// 2018 François Laignel <fengalin@free.fr>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use glib;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst_base;
+use gst_base::prelude::*;
+use gst_base::subclass::prelude::*;
+
+use std::fs::File;
+use std::io::{Read, Seek, SeekFrom};
+use std::sync::Mutex;
+
+use url::Url;
+
+use file_location::FileLocation;
+
+const DEFAULT_LOCATION: Option<FileLocation> = None;
+
+#[derive(Debug)]
+struct Settings {
+ location: Option<FileLocation>,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ location: DEFAULT_LOCATION,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 1] = [subclass::Property("location", |name| {
+ glib::ParamSpec::string(
+ name,
+ "File Location",
+ "Location of the file to read from",
+ None,
+ glib::ParamFlags::READWRITE,
+ )
+})];
+
+enum State {
+ Stopped,
+ Started { file: File, position: u64 },
+}
+
+impl Default for State {
+ fn default() -> State {
+ State::Stopped
+ }
+}
+
+pub struct FileSrc {
+ settings: Mutex<Settings>,
+ state: Mutex<State>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "rsfilesrc",
+ gst::DebugColorFlags::empty(),
+ Some("File Source"),
+ );
+}
+
+impl FileSrc {
+ fn set_location(
+ &self,
+ element: &gst_base::BaseSrc,
+ location: Option<FileLocation>,
+ ) -> Result<(), glib::Error> {
+ let state = self.state.lock().unwrap();
+ if let State::Started { .. } = *state {
+ return Err(glib::Error::new(
+ gst::URIError::BadState,
+ "Changing the `location` property on a started `filesrc` is not supported",
+ ));
+ }
+
+ let mut settings = self.settings.lock().unwrap();
+ settings.location = match location {
+ Some(location) => {
+ if !location.exists() {
+ return Err(glib::Error::new(
+ gst::URIError::BadReference,
+ format!("{} doesn't exist", location).as_str(),
+ ));
+ }
+
+ if !location.is_file() {
+ return Err(glib::Error::new(
+ gst::URIError::BadReference,
+ format!("{} is not a file", location).as_str(),
+ ));
+ }
+
+ match settings.location {
+ Some(ref location_cur) => {
+ gst_info!(
+ CAT,
+ obj: element,
+ "Changing `location` from {:?} to {}",
+ location_cur,
+ location,
+ );
+ }
+ None => {
+ gst_info!(CAT, obj: element, "Setting `location to {}", location,);
+ }
+ }
+ Some(location)
+ }
+ None => {
+ gst_info!(CAT, obj: element, "Resetting `location` to None",);
+ None
+ }
+ };
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for FileSrc {
+ const NAME: &'static str = "RsFileSrc";
+ type ParentType = gst_base::BaseSrc;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn new() -> Self {
+ Self {
+ settings: Mutex::new(Default::default()),
+ state: Mutex::new(Default::default()),
+ }
+ }
+
+ fn type_init(type_: &mut subclass::InitializingType<Self>) {
+ type_.add_interface::<gst::URIHandler>();
+ }
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "File Source",
+ "Source/File",
+ "Read stream from a file",
+ "François Laignel <fengalin@free.fr>, Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+ }
+}
+
+impl ObjectImpl for FileSrc {
+ glib_object_impl!();
+
+ fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+ match *prop {
+ subclass::Property("location", ..) => {
+ let element = obj.downcast_ref::<gst_base::BaseSrc>().unwrap();
+
+ let res = match value.get::<String>() {
+ Ok(Some(location)) => FileLocation::try_from_path_str(location)
+ .and_then(|file_location| self.set_location(&element, Some(file_location))),
+ Ok(None) => self.set_location(&element, None),
+ Err(_) => unreachable!("type checked upstream"),
+ };
+
+ if let Err(err) = res {
+ gst_error!(
+ CAT,
+ obj: element,
+ "Failed to set property `location`: {}",
+ err
+ );
+ }
+ }
+ _ => unimplemented!(),
+ };
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+ match *prop {
+ subclass::Property("location", ..) => {
+ let settings = self.settings.lock().unwrap();
+ let location = settings
+ .location
+ .as_ref()
+ .map(|location| location.to_string());
+
+ Ok(location.to_value())
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst_base::BaseSrc>().unwrap();
+ element.set_format(gst::Format::Bytes);
+ }
+}
+
+impl ElementImpl for FileSrc {}
+
+impl BaseSrcImpl for FileSrc {
+ fn is_seekable(&self, _src: &gst_base::BaseSrc) -> bool {
+ true
+ }
+
+ fn get_size(&self, _src: &gst_base::BaseSrc) -> Option<u64> {
+ let state = self.state.lock().unwrap();
+ if let State::Started { ref file, .. } = *state {
+ file.metadata().ok().map(|m| m.len())
+ } else {
+ None
+ }
+ }
+
+ fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
+ let mut state = self.state.lock().unwrap();
+ if let State::Started { .. } = *state {
+ unreachable!("FileSrc already started");
+ }
+
+ let settings = self.settings.lock().unwrap();
+ let location = settings.location.as_ref().ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["File location is not defined"]
+ )
+ })?;
+
+ let file = File::open(location).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ [
+ "Could not open file {} for reading: {}",
+ location,
+ err.to_string(),
+ ]
+ )
+ })?;
+
+ gst_debug!(CAT, obj: element, "Opened file {:?}", file);
+
+ *state = State::Started { file, position: 0 };
+
+ gst_info!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
+ let mut state = self.state.lock().unwrap();
+ if let State::Stopped = *state {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["FileSrc not started"]
+ ));
+ }
+
+ *state = State::Stopped;
+
+ gst_info!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn fill(
+ &self,
+ element: &gst_base::BaseSrc,
+ offset: u64,
+ _length: u32,
+ buffer: &mut gst::BufferRef,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let mut state = self.state.lock().unwrap();
+
+ let (file, position) = match *state {
+ State::Started {
+ ref mut file,
+ ref mut position,
+ } => (file, position),
+ State::Stopped => {
+ gst_element_error!(element, gst::CoreError::Failed, ["Not started yet"]);
+ return Err(gst::FlowError::Error);
+ }
+ };
+
+ if *position != offset {
+ file.seek(SeekFrom::Start(offset)).map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::LibraryError::Failed,
+ ["Failed to seek to {}: {}", offset, err.to_string()]
+ );
+ gst::FlowError::Error
+ })?;
+
+ *position = offset;
+ }
+
+ let size = {
+ let mut map = buffer.map_writable().map_err(|_| {
+ gst_element_error!(element, gst::LibraryError::Failed, ["Failed to map buffer"]);
+ gst::FlowError::Error
+ })?;
+
+ file.read(map.as_mut()).map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::LibraryError::Failed,
+ ["Failed to read at {}: {}", offset, err.to_string()]
+ );
+ gst::FlowError::Error
+ })?
+ };
+
+ *position += size as u64;
+
+ buffer.set_size(size);
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+}
+
+impl URIHandlerImpl for FileSrc {
+ fn get_uri(&self, _element: &gst::URIHandler) -> Option<String> {
+ let settings = self.settings.lock().unwrap();
+
+ // Conversion to Url already checked while building the `FileLocation`
+ settings.location.as_ref().map(|location| {
+ Url::from_file_path(location)
+ .expect("FileSrc::get_uri couldn't build `Url` from `location`")
+ .into_string()
+ })
+ }
+
+ fn set_uri(&self, element: &gst::URIHandler, uri: &str) -> Result<(), glib::Error> {
+ let element = element.dynamic_cast_ref::<gst_base::BaseSrc>().unwrap();
+
+ // Special case for "file://" as this is used by some applications to test
+ // with `gst_element_make_from_uri` if there's an element that supports the URI protocol
+
+ if uri != "file://" {
+ let file_location = FileLocation::try_from_uri_str(uri)?;
+ self.set_location(&element, Some(file_location))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn get_uri_type() -> gst::URIType {
+ gst::URIType::Src
+ }
+
+ fn get_protocols() -> Vec<String> {
+ vec!["file".to_string()]
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "rsfilesrc",
+ gst::Rank::None,
+ FileSrc::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-file/src/lib.rs b/generic/gst-plugin-file/src/lib.rs
new file mode 100644
index 000000000..90616e87b
--- /dev/null
+++ b/generic/gst-plugin-file/src/lib.rs
@@ -0,0 +1,40 @@
+// Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_type = "cdylib"]
+
+#[macro_use]
+extern crate glib;
+#[macro_use]
+extern crate gstreamer as gst;
+extern crate gstreamer_base as gst_base;
+extern crate url;
+#[macro_use]
+extern crate lazy_static;
+
+mod file_location;
+mod filesink;
+mod filesrc;
+
+fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ filesink::register(plugin)?;
+ filesrc::register(plugin)?;
+ Ok(())
+}
+
+gst_plugin_define!(
+ rsfile,
+ env!("CARGO_PKG_DESCRIPTION"),
+ plugin_init,
+ concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
+ "MIT/X11",
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_REPOSITORY"),
+ env!("BUILD_REL_DATE")
+);
diff --git a/generic/gst-plugin-sodium/Cargo.toml b/generic/gst-plugin-sodium/Cargo.toml
new file mode 100644
index 000000000..85f44983a
--- /dev/null
+++ b/generic/gst-plugin-sodium/Cargo.toml
@@ -0,0 +1,57 @@
+[package]
+name = "gst-plugin-sodium"
+version = "0.1.0"
+authors = ["Jordan Petridis <jordan@centricular.com>"]
+repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugin-rs"
+description = "libsodium-based file encryption and decryption"
+license = "MIT"
+edition = "2018"
+
+[dependencies]
+glib = { git = "https://github.com/gtk-rs/glib" }
+gst = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_14"], package="gstreamer" }
+gst-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_14"], package = "gstreamer-base" }
+sodiumoxide = "0.2.1"
+lazy_static = "1.3.0"
+hex = "0.4"
+smallvec = "1.0"
+
+# example
+clap = { version = "2.33", optional = true }
+serde = { version = "1.0", features = ["derive"], optional = true }
+serde_json = { version = "1.0", optional = true }
+
+[dev-dependencies]
+pretty_assertions = "0.6"
+rand = "0.7"
+
+[dev-dependencies.gst-check]
+git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
+package="gstreamer-check"
+
+[dev-dependencies.gst-app]
+git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
+package="gstreamer-app"
+
+[lib]
+name = "gstsodium"
+crate-type = ["cdylib", "rlib"]
+path = "src/lib.rs"
+
+[[example]]
+name = "generate-keys"
+path = "examples/generate_keys.rs"
+required-features = ["serde", "serde_json", "clap"]
+
+[[example]]
+name = "encrypt-example"
+path = "examples/encrypt_example.rs"
+required-features = ["serde", "serde_json", "clap"]
+
+[[example]]
+name = "decrypt-example"
+path = "examples/decrypt_example.rs"
+required-features = ["serde", "serde_json", "clap"]
+
+[build-dependencies]
+gst-plugin-version-helper = { path="../../gst-plugin-version-helper" }
diff --git a/generic/gst-plugin-sodium/build.rs b/generic/gst-plugin-sodium/build.rs
new file mode 100644
index 000000000..0d1ddb61d
--- /dev/null
+++ b/generic/gst-plugin-sodium/build.rs
@@ -0,0 +1,5 @@
+extern crate gst_plugin_version_helper;
+
+fn main() {
+ gst_plugin_version_helper::get_info()
+}
diff --git a/generic/gst-plugin-sodium/examples/decrypt_example.rs b/generic/gst-plugin-sodium/examples/decrypt_example.rs
new file mode 100644
index 000000000..9908a0fd3
--- /dev/null
+++ b/generic/gst-plugin-sodium/examples/decrypt_example.rs
@@ -0,0 +1,150 @@
+// decrypt_example.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+use glib::prelude::*;
+use gst::prelude::*;
+use sodiumoxide::crypto::box_;
+
+use std::error::Error;
+use std::fs::File;
+use std::path::PathBuf;
+
+use clap::{App, Arg};
+use serde::{Deserialize, Serialize};
+use serde_json;
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Keys {
+ public: box_::PublicKey,
+ private: box_::SecretKey,
+}
+
+impl Keys {
+ fn from_file(file: &PathBuf) -> Result<Self, Box<dyn Error>> {
+ let f = File::open(&file)?;
+ serde_json::from_reader(f).map_err(From::from)
+ }
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let matches = App::new("Decrypt a gstsodium10 file.")
+ .version("1.0")
+ .author("Jordan Petridis <jordan@centricular.com>")
+ .arg(
+ Arg::with_name("input")
+ .short("i")
+ .long("input")
+ .value_name("FILE")
+ .help("File to encrypt")
+ .required(true)
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("output")
+ .short("o")
+ .long("output")
+ .value_name("FILE")
+ .help("File to decrypt")
+ .required(true)
+ .takes_value(true),
+ )
+ .get_matches();
+
+ gst::init()?;
+ gstsodium::plugin_register_static().expect("Failed to register sodium plugin");
+
+ let input_loc = matches.value_of("input").unwrap();
+ let out_loc = matches.value_of("output").unwrap();
+
+ let receiver_keys = {
+ let mut r = PathBuf::new();
+ r.push(env!("CARGO_MANIFEST_DIR"));
+ r.push("examples");
+ r.push("receiver_sample");
+ r.set_extension("json");
+ r
+ };
+
+ let sender_keys = {
+ let mut s = PathBuf::new();
+ s.push(env!("CARGO_MANIFEST_DIR"));
+ s.push("examples");
+ s.push("sender_sample");
+ s.set_extension("json");
+ s
+ };
+
+ let receiver = &Keys::from_file(&receiver_keys)?;
+ let sender = &Keys::from_file(&sender_keys)?;
+
+ let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
+ let decrypter = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ let typefind = gst::ElementFactory::make("typefind", None).unwrap();
+ let filesink = gst::ElementFactory::make("filesink", None).unwrap();
+
+ filesrc
+ .set_property("location", &input_loc)
+ .expect("Failed to set location property");
+ filesink
+ .set_property("location", &out_loc)
+ .expect("Failed to set location property");
+
+ decrypter.set_property("receiver-key", &glib::Bytes::from_owned(receiver.private.0))?;
+ decrypter.set_property("sender-key", &glib::Bytes::from_owned(sender.public))?;
+
+ let pipeline = gst::Pipeline::new(Some("test-pipeline"));
+ pipeline
+ .add_many(&[&filesrc, &decrypter, &typefind, &filesink])
+ .expect("failed to add elements to the pipeline");
+ gst::Element::link_many(&[&filesrc, &decrypter, &typefind, &filesink])
+ .expect("failed to link the elements");
+
+ pipeline
+ .set_state(gst::State::Playing)
+ .expect("Unable to set the pipeline to the `Playing` state");
+
+ let bus = pipeline.get_bus().unwrap();
+ for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Error(err) => {
+ eprintln!(
+ "Error received from element {:?}: {}",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error()
+ );
+ eprintln!("Debugging information: {:?}", err.get_debug());
+ break;
+ }
+ MessageView::Eos(..) => break,
+ _ => (),
+ }
+ }
+
+ pipeline
+ .set_state(gst::State::Null)
+ .expect("Unable to set the pipeline to the `Playing` state");
+
+ Ok(())
+}
diff --git a/generic/gst-plugin-sodium/examples/encrypt_example.rs b/generic/gst-plugin-sodium/examples/encrypt_example.rs
new file mode 100644
index 000000000..6189e3242
--- /dev/null
+++ b/generic/gst-plugin-sodium/examples/encrypt_example.rs
@@ -0,0 +1,145 @@
+// encrypt_example.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+use glib::prelude::*;
+use gst::prelude::*;
+use sodiumoxide::crypto::box_;
+
+use std::error::Error;
+use std::fs::File;
+use std::path::PathBuf;
+
+use clap::{App, Arg};
+use serde::{Deserialize, Serialize};
+use serde_json;
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Keys {
+ public: box_::PublicKey,
+ private: box_::SecretKey,
+}
+
+impl Keys {
+ fn from_file(file: &PathBuf) -> Result<Self, Box<dyn Error>> {
+ let f = File::open(&file)?;
+ serde_json::from_reader(f).map_err(From::from)
+ }
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let matches = App::new("Encrypt a file with in the gstsodium10 format")
+ .version("1.0")
+ .author("Jordan Petridis <jordan@centricular.com>")
+ .arg(
+ Arg::with_name("input")
+ .short("i")
+ .long("input")
+ .value_name("FILE")
+ .help("File to encrypt")
+ .required(true)
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("output")
+ .short("o")
+ .long("output")
+ .value_name("FILE")
+ .help("File to decrypt")
+ .required(true)
+ .takes_value(true),
+ )
+ .get_matches();
+
+ gst::init()?;
+ gstsodium::plugin_register_static().expect("Failed to register sodium plugin");
+
+ let input_loc = matches.value_of("input").unwrap();
+ let out_loc = matches.value_of("output").unwrap();
+
+ let receiver_keys = {
+ let mut r = PathBuf::new();
+ r.push(env!("CARGO_MANIFEST_DIR"));
+ r.push("examples");
+ r.push("receiver_sample");
+ r.set_extension("json");
+ r
+ };
+
+ let sender_keys = {
+ let mut s = PathBuf::new();
+ s.push(env!("CARGO_MANIFEST_DIR"));
+ s.push("examples");
+ s.push("sender_sample");
+ s.set_extension("json");
+ s
+ };
+
+ let receiver = &Keys::from_file(&receiver_keys)?;
+ let sender = &Keys::from_file(&sender_keys)?;
+
+ let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
+ let encrypter = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ let filesink = gst::ElementFactory::make("filesink", None).unwrap();
+
+ filesrc
+ .set_property("location", &input_loc)
+ .expect("Failed to set location property");
+ filesink
+ .set_property("location", &out_loc)
+ .expect("Failed to set location property");
+
+ encrypter.set_property("receiver-key", &glib::Bytes::from_owned(receiver.public))?;
+ encrypter.set_property("sender-key", &glib::Bytes::from_owned(sender.private.0))?;
+
+ let pipeline = gst::Pipeline::new(Some("test-pipeline"));
+ pipeline
+ .add_many(&[&filesrc, &encrypter, &filesink])
+ .expect("failed to add elements to the pipeline");
+ gst::Element::link_many(&[&filesrc, &encrypter, &filesink])
+ .expect("failed to link the elements");
+
+ pipeline.set_state(gst::State::Playing)?;
+
+ let bus = pipeline.get_bus().unwrap();
+ for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Error(err) => {
+ eprintln!(
+ "Error received from element {:?}: {}",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error()
+ );
+ eprintln!("Debugging information: {:?}", err.get_debug());
+ break;
+ }
+ MessageView::Eos(..) => break,
+ _ => (),
+ }
+ }
+
+ pipeline.set_state(gst::State::Null)?;
+
+ Ok(())
+}
diff --git a/generic/gst-plugin-sodium/examples/generate_keys.rs b/generic/gst-plugin-sodium/examples/generate_keys.rs
new file mode 100644
index 000000000..b7de47772
--- /dev/null
+++ b/generic/gst-plugin-sodium/examples/generate_keys.rs
@@ -0,0 +1,102 @@
+// generate_keys.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+use clap::{App, Arg};
+use serde::{Deserialize, Serialize};
+use serde_json;
+use sodiumoxide::crypto::box_;
+use std::fs::File;
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Keys {
+ public: box_::PublicKey,
+ private: box_::SecretKey,
+}
+
+impl Keys {
+ fn new() -> Self {
+ let (public, private) = box_::gen_keypair();
+ Keys { public, private }
+ }
+
+ fn write_to_file(&self, path: &str, json: bool) {
+ if json {
+ let path = if !path.ends_with(".json") {
+ format!("{}.json", path)
+ } else {
+ path.into()
+ };
+
+ let file =
+ File::create(&path).unwrap_or_else(|_| panic!("Failed to create file at {}", path));
+ serde_json::to_writer(file, &self)
+ .unwrap_or_else(|_| panic!("Failed to write to file at {}", path));
+ } else {
+ use std::io::Write;
+ use std::path::PathBuf;
+
+ let mut private = PathBuf::from(path);
+ private.set_extension("prv");
+ let mut file = File::create(&private)
+ .unwrap_or_else(|_| panic!("Failed to create file at {}", private.display()));
+ file.write_all(&self.private.0)
+ .unwrap_or_else(|_| panic!("Failed to write to file at {}", private.display()));
+
+ let mut public = PathBuf::from(path);
+ public.set_extension("pub");
+ let mut file = File::create(&public)
+ .unwrap_or_else(|_| panic!("Failed to create file at {}", public.display()));
+ file.write_all(self.public.as_ref())
+ .unwrap_or_else(|_| panic!("Failed to write to file at {}", public.display()));
+ }
+ }
+}
+
+fn main() {
+ let matches = App::new("Generate the keys to be used with the sodium element")
+ .version("1.0")
+ .author("Jordan Petridis <jordan@centricular.com>")
+ .about("Generate a pair of Sodium's crypto_box_curve25519xsalsa20poly1305 keys.")
+ .arg(
+ Arg::with_name("path")
+ .long("path")
+ .short("p")
+ .value_name("FILE")
+ .help("Path to write the Keys")
+ .required(true)
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("json")
+ .long("json")
+ .short("j")
+ .help("Write a JSON file instead of a key.prv/key.pub pair"),
+ )
+ .get_matches();
+
+ let keys = Keys::new();
+
+ let path = matches.value_of("path").unwrap();
+ keys.write_to_file(path, matches.is_present("json"));
+}
diff --git a/generic/gst-plugin-sodium/examples/receiver_sample.json b/generic/gst-plugin-sodium/examples/receiver_sample.json
new file mode 100644
index 000000000..619b365f8
--- /dev/null
+++ b/generic/gst-plugin-sodium/examples/receiver_sample.json
@@ -0,0 +1,2 @@
+
+{"public":[28,95,33,124,28,103,80,78,7,28,234,40,226,179,253,166,169,64,78,5,57,92,151,179,221,89,68,70,44,225,219,19],"private":[54,221,217,54,94,235,167,2,187,249,71,31,59,27,19,166,78,236,102,48,29,142,41,189,22,146,218,69,147,165,240,235]}
diff --git a/generic/gst-plugin-sodium/examples/sender_sample.json b/generic/gst-plugin-sodium/examples/sender_sample.json
new file mode 100644
index 000000000..d7ab1fa6c
--- /dev/null
+++ b/generic/gst-plugin-sodium/examples/sender_sample.json
@@ -0,0 +1 @@
+{"public":[66,248,199,74,216,55,228,116,52,17,147,56,65,130,134,148,157,153,235,171,179,147,120,71,100,243,133,120,160,14,111,65],"private":[154,227,90,239,206,184,202,234,176,161,14,91,218,98,142,13,145,223,210,222,224,240,98,51,142,165,255,1,159,100,242,162]}
diff --git a/generic/gst-plugin-sodium/src/decrypter.rs b/generic/gst-plugin-sodium/src/decrypter.rs
new file mode 100644
index 000000000..39891ee17
--- /dev/null
+++ b/generic/gst-plugin-sodium/src/decrypter.rs
@@ -0,0 +1,733 @@
+// decrypter.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use sodiumoxide::crypto::box_;
+
+use std::sync::Mutex;
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = {
+ gst::DebugCategory::new(
+ "sodiumdecrypter",
+ gst::DebugColorFlags::empty(),
+ Some("Decrypter Element"),
+ )
+ };
+}
+
+static PROPERTIES: [subclass::Property; 2] = [
+ subclass::Property("receiver-key", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Receiver Key",
+ "The private key of the Reeiver",
+ glib::Bytes::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("sender-key", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Sender Key",
+ "The public key of the Sender",
+ glib::Bytes::static_type(),
+ glib::ParamFlags::WRITABLE,
+ )
+ }),
+];
+
+#[derive(Debug, Clone, Default)]
+struct Props {
+ receiver_key: Option<glib::Bytes>,
+ sender_key: Option<glib::Bytes>,
+}
+
+#[derive(Debug)]
+struct State {
+ adapter: gst_base::UniqueAdapter,
+ initial_nonce: Option<box_::Nonce>,
+ precomputed_key: box_::PrecomputedKey,
+ block_size: Option<u32>,
+}
+
+impl State {
+ fn from_props(props: &Props) -> Result<Self, gst::ErrorMessage> {
+ let sender_key = props
+ .sender_key
+ .as_ref()
+ .and_then(|k| box_::PublicKey::from_slice(&k))
+ .ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::NotFound,
+ [format!(
+ "Failed to set Sender's Key from property: {:?}",
+ props.sender_key
+ )
+ .as_ref()]
+ )
+ })?;
+
+ let receiver_key = props
+ .receiver_key
+ .as_ref()
+ .and_then(|k| box_::SecretKey::from_slice(&k))
+ .ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::NotFound,
+ [format!(
+ "Failed to set Receiver's Key from property: {:?}",
+ props.receiver_key
+ )
+ .as_ref()]
+ )
+ })?;
+
+ let precomputed_key = box_::precompute(&sender_key, &receiver_key);
+
+ Ok(Self {
+ adapter: gst_base::UniqueAdapter::new(),
+ precomputed_key,
+ initial_nonce: None,
+ block_size: None,
+ })
+ }
+
+ // Split the buffer into N(`chunk_index`) chunks of `block_size`,
+ // decrypt them, and push them to the internal adapter for further
+ // retrieval
+ fn decrypt_into_adapter(
+ &mut self,
+ element: &gst::Element,
+ pad: &gst::Pad,
+ buffer: &gst::Buffer,
+ chunk_index: u64,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let map = buffer.map_readable().map_err(|_| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Format,
+ ["Failed to map buffer readable"]
+ );
+
+ gst::FlowError::Error
+ })?;
+
+ gst_debug!(CAT, obj: pad, "Returned pull size: {}", map.len());
+
+ let mut nonce = add_nonce(self.initial_nonce.unwrap(), chunk_index);
+ let block_size = self.block_size.expect("Block size wasn't set") as usize + box_::MACBYTES;
+
+ for subbuffer in map.chunks(block_size) {
+ let plain = box_::open_precomputed(&subbuffer, &nonce, &self.precomputed_key).map_err(
+ |_| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Format,
+ ["Failed to decrypt buffer"]
+ );
+ gst::FlowError::Error
+ },
+ )?;
+ // assumes little endian
+ nonce.increment_le_inplace();
+ self.adapter.push(gst::Buffer::from_mut_slice(plain));
+ }
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+
+ // Retrieve the requested buffer out of the adapter.
+ fn get_requested_buffer(
+ &mut self,
+ pad: &gst::Pad,
+ buffer: Option<&mut gst::BufferRef>,
+ requested_size: u32,
+ adapter_offset: usize,
+ ) -> Result<gst::PadGetRangeSuccess, gst::FlowError> {
+ let avail = self.adapter.available();
+ gst_debug!(CAT, obj: pad, "Avail: {}", avail);
+ gst_debug!(CAT, obj: pad, "Adapter offset: {}", adapter_offset);
+
+ // if this underflows, the available buffer in the adapter is smaller than the
+ // requested offset, which means we have reached EOS
+ let available_buffer = avail
+ .checked_sub(adapter_offset)
+ .ok_or(gst::FlowError::Eos)?;
+
+ // if the available buffer size is smaller than the requested, it's a short
+ // read and return that. Else return the requested size
+ let available_size = if available_buffer <= requested_size as usize {
+ available_buffer
+ } else {
+ requested_size as usize
+ };
+
+ if available_size == 0 {
+ self.adapter.clear();
+
+ // if the requested buffer was 0 sized, return an
+ // empty buffer
+ if requested_size == 0 {
+ if let Some(buffer) = buffer {
+ buffer.set_size(0);
+ return Ok(gst::PadGetRangeSuccess::FilledBuffer);
+ } else {
+ return Ok(gst::PadGetRangeSuccess::NewBuffer(gst::Buffer::new()));
+ }
+ }
+
+ return Err(gst::FlowError::Eos);
+ }
+
+ // discard what we don't need
+ assert!(self.adapter.available() >= adapter_offset);
+ self.adapter.flush(adapter_offset);
+
+ assert!(self.adapter.available() >= available_size);
+ let res = if let Some(buffer) = buffer {
+ let mut map = match buffer.map_writable() {
+ Ok(map) => map,
+ Err(_) => {
+ gst_error!(CAT, obj: pad, "Failed to map provided buffer writable");
+ return Err(gst::FlowError::Error);
+ }
+ };
+ self.adapter.copy(0, &mut map[..available_size]);
+ if map.len() != available_size {
+ drop(map);
+ buffer.set_size(available_size);
+ }
+ gst::PadGetRangeSuccess::FilledBuffer
+ } else {
+ let buffer = self
+ .adapter
+ .take_buffer(available_size)
+ .expect("Failed to get buffer from adapter");
+ gst::PadGetRangeSuccess::NewBuffer(buffer)
+ };
+
+ // Cleanup the adapter
+ self.adapter.clear();
+
+ Ok(res)
+ }
+}
+
+/// Calculate the nonce of a block based on the initial nonce
+/// and the block index in the stream.
+///
+/// This is a faster way of doing `(0..chunk_index).for_each(|_| nonce.increment_le_inplace());`
+fn add_nonce(initial_nonce: box_::Nonce, chunk_index: u64) -> box_::Nonce {
+ let mut nonce = initial_nonce.0;
+ // convert our index to a bytes array
+ // add padding so our 8byte array of the chunk_index will have an
+ // equal length with the nonce, padding at the end cause little endian
+ let idx = chunk_index.to_le_bytes();
+ let idx = &[
+ idx[0], idx[1], idx[2], idx[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ assert_eq!(idx.len(), box_::NONCEBYTES);
+
+ // add the chunk index to the nonce
+ sodiumoxide::utils::add_le(&mut nonce, idx).expect("Failed to calculate the nonce");
+
+ // construct back a nonce from our custom array
+ box_::Nonce::from_slice(&nonce).expect("Failed to convert slice back to Nonce")
+}
+
+struct Decrypter {
+ srcpad: gst::Pad,
+ sinkpad: gst::Pad,
+ props: Mutex<Props>,
+ state: Mutex<Option<State>>,
+}
+
+impl Decrypter {
+ fn set_pad_functions(_sinkpad: &gst::Pad, srcpad: &gst::Pad) {
+ srcpad.set_getrange_function(|pad, parent, offset, buffer, size| {
+ Decrypter::catch_panic_pad_function(
+ parent,
+ || Err(gst::FlowError::Error),
+ |decrypter, element| decrypter.get_range(pad, element, offset, buffer, size),
+ )
+ });
+
+ srcpad.set_activatemode_function(|pad, parent, mode, active| {
+ Decrypter::catch_panic_pad_function(
+ parent,
+ || {
+ Err(gst_loggable_error!(
+ CAT,
+ "Panic activating srcpad with mode"
+ ))
+ },
+ |decrypter, element| {
+ decrypter.src_activatemode_function(pad, element, mode, active)
+ },
+ )
+ });
+
+ srcpad.set_query_function(|pad, parent, query| {
+ Decrypter::catch_panic_pad_function(
+ parent,
+ || false,
+ |decrypter, element| decrypter.src_query(pad, element, query),
+ )
+ });
+ }
+
+ fn src_activatemode_function(
+ &self,
+ _pad: &gst::Pad,
+ element: &gst::Element,
+ mode: gst::PadMode,
+ active: bool,
+ ) -> Result<(), gst::LoggableError> {
+ match mode {
+ gst::PadMode::Pull => {
+ self.sinkpad
+ .activate_mode(mode, active)
+ .map_err(gst::LoggableError::from)?;
+
+ // Set the nonce and block size from the headers
+ // right after we activate the pad
+ self.check_headers(element)
+ }
+ gst::PadMode::Push => Err(gst_loggable_error!(CAT, "Push mode not supported")),
+ _ => Err(gst_loggable_error!(
+ CAT,
+ "Failed to activate the pad in Unknown mode, {:?}",
+ mode
+ )),
+ }
+ }
+
+ fn src_query(&self, pad: &gst::Pad, element: &gst::Element, query: &mut gst::QueryRef) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad, "Handling query {:?}", query);
+
+ match query.view_mut() {
+ QueryView::Scheduling(mut q) => {
+ let mut peer_query = gst::Query::new_scheduling();
+ let res = self.sinkpad.peer_query(&mut peer_query);
+ if !res {
+ return res;
+ }
+
+ gst_log!(CAT, obj: pad, "Upstream returned {:?}", peer_query);
+
+ let (flags, min, max, align) = peer_query.get_result();
+ q.set(flags, min, max, align);
+ q.add_scheduling_modes(&[gst::PadMode::Pull]);
+ gst_log!(CAT, obj: pad, "Returning {:?}", q.get_mut_query());
+ true
+ }
+ QueryView::Duration(ref mut q) => {
+ use std::convert::TryInto;
+
+ if q.get_format() != gst::Format::Bytes {
+ return pad.query_default(Some(element), query);
+ }
+
+ /* First let's query the bytes duration upstream */
+ let mut peer_query = gst::query::Query::new_duration(gst::Format::Bytes);
+
+ if !self.sinkpad.peer_query(&mut peer_query) {
+ gst_error!(CAT, "Failed to query upstream duration");
+ return false;
+ }
+
+ let size = match peer_query.get_result().try_into().unwrap() {
+ gst::format::Bytes(Some(size)) => size,
+ gst::format::Bytes(None) => {
+ gst_error!(CAT, "Failed to query upstream duration");
+
+ return false;
+ }
+ };
+
+ let state = self.state.lock().unwrap();
+ let state = match state.as_ref() {
+ // If state isn't set, it means that the
+ // element hasn't been activated yet.
+ None => return false,
+ Some(s) => s,
+ };
+
+ // subtract static offsets
+ let size = size - super::HEADERS_SIZE as u64;
+
+ // calculate the number of chunks that exist in the stream
+ let total_chunks =
+ (size - 1) / state.block_size.expect("Block size wasn't set") as u64;
+ // subtrack the MAC of each block
+ let size = size - total_chunks * box_::MACBYTES as u64;
+
+ gst_debug!(CAT, obj: pad, "Setting duration bytes: {}", size);
+ q.set(gst::format::Bytes::from(size));
+
+ true
+ }
+ _ => pad.query_default(Some(element), query),
+ }
+ }
+
+ fn check_headers(&self, element: &gst::Element) -> Result<(), gst::LoggableError> {
+ let is_none = {
+ let mutex_state = self.state.lock().unwrap();
+ let state = mutex_state.as_ref().unwrap();
+ state.initial_nonce.is_none()
+ };
+
+ if !is_none {
+ return Ok(());
+ }
+
+ let buffer = self
+ .sinkpad
+ .pull_range(0, crate::HEADERS_SIZE as u32)
+ .map_err(|err| {
+ let err = gst_loggable_error!(
+ CAT,
+ "Failed to pull nonce from the stream, reason: {:?}",
+ err
+ );
+ err.log_with_object(element);
+ err
+ })?;
+
+ if buffer.get_size() != crate::HEADERS_SIZE {
+ let err = gst_loggable_error!(CAT, "Headers buffer has wrong size");
+ err.log_with_object(element);
+ return Err(err);
+ }
+
+ let map = buffer.map_readable().map_err(|_| {
+ let err = gst_loggable_error!(CAT, "Failed to map buffer readable");
+ err.log_with_object(element);
+ err
+ })?;
+
+ let sodium_header_slice = &map[..crate::TYPEFIND_HEADER_SIZE];
+ if sodium_header_slice != crate::TYPEFIND_HEADER {
+ let err = gst_loggable_error!(CAT, "Buffer has wrong typefind header");
+ err.log_with_object(element);
+ return Err(err);
+ }
+
+ let nonce_slice =
+ &map[crate::TYPEFIND_HEADER_SIZE..crate::TYPEFIND_HEADER_SIZE + box_::NONCEBYTES];
+ assert_eq!(nonce_slice.len(), box_::NONCEBYTES);
+ let nonce = box_::Nonce::from_slice(nonce_slice).ok_or_else(|| {
+ let err = gst_loggable_error!(CAT, "Failed to create nonce from buffer");
+ err.log_with_object(&self.srcpad);
+ err
+ })?;
+
+ let slice = &map[crate::TYPEFIND_HEADER_SIZE + box_::NONCEBYTES..crate::HEADERS_SIZE];
+ assert_eq!(
+ crate::HEADERS_SIZE - crate::TYPEFIND_HEADER_SIZE - box_::NONCEBYTES,
+ 4
+ );
+ let block_size = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]);
+
+ // reacquire the lock again to change the state
+ let mut state = self.state.lock().unwrap();
+ let state = state.as_mut().unwrap();
+
+ state.initial_nonce = Some(nonce);
+ gst_debug!(CAT, obj: element, "Setting nonce to: {:?}", nonce.0);
+ state.block_size = Some(block_size);
+ gst_debug!(CAT, obj: element, "Setting block size to: {}", block_size);
+
+ Ok(())
+ }
+
+ fn pull_requested_buffer(
+ &self,
+ pad: &gst::Pad,
+ element: &gst::Element,
+ requested_size: u32,
+ block_size: u32,
+ chunk_index: u64,
+ ) -> Result<gst::Buffer, gst::FlowError> {
+ let pull_offset = super::HEADERS_SIZE as u64
+ + (chunk_index * block_size as u64)
+ + (chunk_index * box_::MACBYTES as u64);
+
+ gst_debug!(CAT, obj: pad, "Pull offset: {}", pull_offset);
+ gst_debug!(CAT, obj: pad, "block size: {}", block_size);
+
+ // calculate how many chunks are needed, if we need something like 3.2
+ // round the number to 4 and cut the buffer afterwards.
+ let checked = requested_size.checked_add(block_size).ok_or_else(|| {
+ gst_element_error!(
+ element,
+ gst::LibraryError::Failed,
+ [
+ "Addition overflow when adding requested pull size and block size: {} + {}",
+ requested_size,
+ block_size,
+ ]
+ );
+ gst::FlowError::Error
+ })?;
+
+ // Read at least one chunk in case 0 bytes were requested
+ let total_chunks = u32::max((checked - 1) / block_size, 1);
+ gst_debug!(CAT, obj: pad, "Blocks to be pulled: {}", total_chunks);
+
+ // Pull a buffer of all the chunks we will need
+ let checked_size = total_chunks.checked_mul(block_size).ok_or_else(|| {
+ gst_element_error!(
+ element,
+ gst::LibraryError::Failed,
+ [
+ "Overflowed trying to calculate the buffer size to pull: {} * {}",
+ total_chunks,
+ block_size,
+ ]
+ );
+ gst::FlowError::Error
+ })?;
+
+ let total_size = checked_size + (total_chunks * box_::MACBYTES as u32);
+ gst_debug!(CAT, obj: pad, "Requested pull size: {}", total_size);
+
+ self.sinkpad.pull_range(pull_offset, total_size).map_err(|err| {
+ match err {
+ gst::FlowError::Flushing => {
+ gst_debug!(CAT, obj: &self.sinkpad, "Pausing after pulling buffer, reason: flushing");
+ }
+ gst::FlowError::Eos => {
+ gst_debug!(CAT, obj: &self.sinkpad, "Eos");
+ }
+ flow => {
+ gst_error!(CAT, obj: &self.sinkpad, "Failed to pull, reason: {:?}", flow);
+ }
+ };
+
+ err
+ })
+ }
+
+ fn get_range(
+ &self,
+ pad: &gst::Pad,
+ element: &gst::Element,
+ offset: u64,
+ buffer: Option<&mut gst::BufferRef>,
+ requested_size: u32,
+ ) -> Result<gst::PadGetRangeSuccess, gst::FlowError> {
+ let block_size = {
+ let mut mutex_state = self.state.lock().unwrap();
+ // This will only be run after READY state,
+ // and will be guaranted to be initialized
+ let state = mutex_state.as_mut().unwrap();
+ // Cleanup the adapter
+ state.adapter.clear();
+ state.block_size.expect("Block size wasn't set")
+ };
+
+ gst_debug!(CAT, obj: pad, "Requested offset: {}", offset);
+ gst_debug!(CAT, obj: pad, "Requested size: {}", requested_size);
+
+ let chunk_index = offset as u64 / block_size as u64;
+ gst_debug!(CAT, obj: pad, "Stream Block index: {}", chunk_index);
+
+ let pull_offset = offset - (chunk_index * block_size as u64);
+ assert!(pull_offset <= std::u32::MAX as u64);
+ let pull_offset = pull_offset as u32;
+
+ let pulled_buffer = self.pull_requested_buffer(
+ pad,
+ element,
+ requested_size + pull_offset,
+ block_size,
+ chunk_index,
+ )?;
+
+ let mut state = self.state.lock().unwrap();
+ // This will only be run after READY state,
+ // and will be guaranted to be initialized
+ let state = state.as_mut().unwrap();
+
+ state.decrypt_into_adapter(element, &self.srcpad, &pulled_buffer, chunk_index)?;
+
+ let adapter_offset = pull_offset as usize;
+ state.get_requested_buffer(&self.srcpad, buffer, requested_size, adapter_offset)
+ }
+}
+
+impl ObjectSubclass for Decrypter {
+ const NAME: &'static str = "RsSodiumDecryptor";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ let templ = klass.get_pad_template("sink").unwrap();
+ let sinkpad = gst::Pad::new_from_template(&templ, Some("sink"));
+ let templ = klass.get_pad_template("src").unwrap();
+ let srcpad = gst::Pad::new_from_template(&templ, Some("src"));
+
+ Decrypter::set_pad_functions(&sinkpad, &srcpad);
+ let props = Mutex::new(Props::default());
+ let state = Mutex::new(None);
+
+ Self {
+ srcpad,
+ sinkpad,
+ props,
+ state,
+ }
+ }
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Decrypter",
+ "Generic",
+ "libsodium-based file decrypter",
+ "Jordan Petridis <jordan@centricular.com>",
+ );
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &gst::Caps::new_any(),
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ let sink_caps = gst::Caps::builder("application/x-sodium-encrypted").build();
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &sink_caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+ klass.install_properties(&PROPERTIES);
+ }
+}
+
+impl ObjectImpl for Decrypter {
+ glib_object_impl!();
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(&self.sinkpad).unwrap();
+ element.add_pad(&self.srcpad).unwrap();
+ }
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("sender-key", ..) => {
+ let mut props = self.props.lock().unwrap();
+ props.sender_key = value.get().expect("type checked upstream");
+ }
+
+ subclass::Property("receiver-key", ..) => {
+ let mut props = self.props.lock().unwrap();
+ props.receiver_key = value.get().expect("type checked upstream");
+ }
+
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("receiver-key", ..) => {
+ let props = self.props.lock().unwrap();
+ Ok(props.receiver_key.to_value())
+ }
+
+ _ => unimplemented!(),
+ }
+ }
+}
+
+impl ElementImpl for Decrypter {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_debug!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ let props = self.props.lock().unwrap().clone();
+
+ // Create an internal state struct from the provided properties or
+ // refuse to change state
+ let state_ = State::from_props(&props).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+
+ let mut state = self.state.lock().unwrap();
+ *state = Some(state_);
+ }
+ gst::StateChange::ReadyToNull => {
+ let _ = self.state.lock().unwrap().take();
+ }
+ _ => (),
+ }
+
+ let success = self.parent_change_state(element, transition)?;
+
+ if transition == gst::StateChange::ReadyToNull {
+ let _ = self.state.lock().unwrap().take();
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "sodiumdecrypter",
+ gst::Rank::None,
+ Decrypter::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-sodium/src/encrypter.rs b/generic/gst-plugin-sodium/src/encrypter.rs
new file mode 100644
index 000000000..60a6e01e8
--- /dev/null
+++ b/generic/gst-plugin-sodium/src/encrypter.rs
@@ -0,0 +1,572 @@
+// encrypter.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use smallvec::SmallVec;
+use sodiumoxide::crypto::box_;
+
+type BufferVec = SmallVec<[gst::Buffer; 16]>;
+
+use std::sync::Mutex;
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = {
+ gst::DebugCategory::new(
+ "sodiumencrypter",
+ gst::DebugColorFlags::empty(),
+ Some("Encrypter Element"),
+ )
+ };
+}
+
+static PROPERTIES: [subclass::Property; 3] = [
+ subclass::Property("receiver-key", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Receiver Key",
+ "The public key of the Receiver",
+ glib::Bytes::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("sender-key", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Sender Key",
+ "The private key of the Sender",
+ glib::Bytes::static_type(),
+ glib::ParamFlags::WRITABLE,
+ )
+ }),
+ subclass::Property("block-size", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Block Size",
+ "The block-size of the chunks",
+ 1024,
+ std::u32::MAX,
+ 32768,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Debug, Clone)]
+struct Props {
+ receiver_key: Option<glib::Bytes>,
+ sender_key: Option<glib::Bytes>,
+ block_size: u32,
+}
+
+impl Default for Props {
+ fn default() -> Self {
+ Props {
+ receiver_key: None,
+ sender_key: None,
+ block_size: 32768,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct State {
+ adapter: gst_base::UniqueAdapter,
+ nonce: box_::Nonce,
+ precomputed_key: box_::PrecomputedKey,
+ block_size: u32,
+ write_headers: bool,
+}
+
+impl State {
+ fn from_props(props: &Props) -> Result<Self, gst::ErrorMessage> {
+ let sender_key = props
+ .sender_key
+ .as_ref()
+ .and_then(|k| box_::SecretKey::from_slice(&k))
+ .ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::NotFound,
+ [format!(
+ "Failed to set Sender's Key from property: {:?}",
+ props.sender_key
+ )
+ .as_ref()]
+ )
+ })?;
+
+ let receiver_key = props
+ .receiver_key
+ .as_ref()
+ .and_then(|k| box_::PublicKey::from_slice(&k))
+ .ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::NotFound,
+ [format!(
+ "Failed to set Receiver's Key from property: {:?}",
+ props.receiver_key
+ )
+ .as_ref()]
+ )
+ })?;
+
+ // This env variable is only meant to bypass nonce regeneration during
+ // tests to get determinisic results. It should never be used outside
+ // of testing environments.
+ let nonce = if let Ok(val) = std::env::var("GST_SODIUM_ENCRYPT_NONCE") {
+ let bytes = hex::decode(val).expect("Failed to decode hex variable");
+ assert_eq!(bytes.len(), box_::NONCEBYTES);
+ box_::Nonce::from_slice(&bytes).unwrap()
+ } else {
+ box_::gen_nonce()
+ };
+
+ let precomputed_key = box_::precompute(&receiver_key, &sender_key);
+
+ Ok(Self {
+ adapter: gst_base::UniqueAdapter::new(),
+ precomputed_key,
+ nonce,
+ block_size: props.block_size,
+ write_headers: true,
+ })
+ }
+
+ fn seal(&mut self, message: &[u8]) -> Vec<u8> {
+ let ciphertext = box_::seal_precomputed(message, &self.nonce, &self.precomputed_key);
+ self.nonce.increment_le_inplace();
+ ciphertext
+ }
+
+ fn encrypt_message(&mut self, buffer: &gst::BufferRef) -> gst::Buffer {
+ let map = buffer
+ .map_readable()
+ .expect("Failed to map buffer readable");
+
+ let sealed = self.seal(&map);
+ gst::Buffer::from_mut_slice(sealed)
+ }
+
+ fn encrypt_blocks(&mut self, block_size: usize) -> Result<BufferVec, gst::FlowError> {
+ assert_ne!(block_size, 0);
+
+ let mut buffers = BufferVec::new();
+
+ // As long we have enough bytes to encrypt a block, or more, we do so
+ // else the leftover bytes on the adapter will be pushed when EOS
+ // is sent.
+ while self.adapter.available() >= block_size {
+ let buffer = self.adapter.take_buffer(block_size).unwrap();
+ let out_buf = self.encrypt_message(&buffer);
+
+ buffers.push(out_buf);
+ }
+
+ Ok(buffers)
+ }
+}
+
+struct Encrypter {
+ srcpad: gst::Pad,
+ sinkpad: gst::Pad,
+ props: Mutex<Props>,
+ state: Mutex<Option<State>>,
+}
+
+impl Encrypter {
+ fn set_pad_functions(sinkpad: &gst::Pad, srcpad: &gst::Pad) {
+ sinkpad.set_chain_function(|pad, parent, buffer| {
+ Encrypter::catch_panic_pad_function(
+ parent,
+ || Err(gst::FlowError::Error),
+ |encrypter, element| encrypter.sink_chain(pad, element, buffer),
+ )
+ });
+ sinkpad.set_event_function(|pad, parent, event| {
+ Encrypter::catch_panic_pad_function(
+ parent,
+ || false,
+ |encrypter, element| encrypter.sink_event(pad, element, event),
+ )
+ });
+
+ srcpad.set_query_function(|pad, parent, query| {
+ Encrypter::catch_panic_pad_function(
+ parent,
+ || false,
+ |encrypter, element| encrypter.src_query(pad, element, query),
+ )
+ });
+ srcpad.set_event_function(|pad, parent, event| {
+ Encrypter::catch_panic_pad_function(
+ parent,
+ || false,
+ |encrypter, element| encrypter.src_event(pad, element, event),
+ )
+ });
+ }
+
+ fn sink_chain(
+ &self,
+ pad: &gst::Pad,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer);
+
+ let mut buffers = BufferVec::new();
+ let mut state_guard = self.state.lock().unwrap();
+ let state = state_guard.as_mut().unwrap();
+
+ if state.write_headers {
+ let mut headers = Vec::with_capacity(40);
+ headers.extend_from_slice(crate::TYPEFIND_HEADER);
+ // Write the Nonce used into the stream.
+ headers.extend_from_slice(state.nonce.as_ref());
+ // Write the block_size into the stream
+ headers.extend_from_slice(&state.block_size.to_le_bytes());
+
+ buffers.push(gst::Buffer::from_mut_slice(headers));
+ state.write_headers = false;
+ }
+
+ state.adapter.push(buffer);
+
+ // Encrypt the whole blocks, if any, and push them.
+ buffers.extend(
+ state
+ .encrypt_blocks(state.block_size as usize)
+ .map_err(|err| {
+ // log the error to the bus
+ gst_element_error!(
+ element,
+ gst::ResourceError::Write,
+ ["Failed to decrypt buffer"]
+ );
+ err
+ })?,
+ );
+
+ drop(state_guard);
+
+ for buffer in buffers {
+ self.srcpad.push(buffer).map_err(|err| {
+ gst_error!(CAT, obj: element, "Failed to push buffer {:?}", err);
+ err
+ })?;
+ }
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+
+ fn sink_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad, "Handling event {:?}", event);
+
+ match event.view() {
+ EventView::Caps(_) => {
+ // We send our own caps downstream
+ let caps = gst::Caps::builder("application/x-sodium-encrypted").build();
+ self.srcpad.push_event(gst::Event::new_caps(&caps).build())
+ }
+ EventView::Eos(_) => {
+ let mut state_mutex = self.state.lock().unwrap();
+ let mut buffers = BufferVec::new();
+ // This will only be run after READY state,
+ // and will be guaranted to be initialized
+ let state = state_mutex.as_mut().unwrap();
+
+ // Now that all the full size blocks are pushed, drain the
+ // rest of the adapter and push whatever is left.
+ let avail = state.adapter.available();
+ // logic error, all the complete blocks that can be pushed
+ // should have been done in the sink_chain call.
+ assert!(avail < state.block_size as usize);
+
+ if avail > 0 {
+ match state.encrypt_blocks(avail) {
+ Err(_) => {
+ gst_element_error!(
+ element,
+ gst::ResourceError::Write,
+ ["Failed to encrypt buffers at EOS"]
+ );
+ return false;
+ }
+ Ok(b) => buffers.extend(b),
+ }
+ }
+
+ // drop the lock before pushing into the pad
+ drop(state_mutex);
+
+ for buffer in buffers {
+ if let Err(err) = self.srcpad.push(buffer) {
+ gst_error!(CAT, obj: element, "Failed to push buffer at EOS {:?}", err);
+ return false;
+ }
+ }
+
+ pad.event_default(Some(element), event)
+ }
+ _ => pad.event_default(Some(element), event),
+ }
+ }
+
+ fn src_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad, "Handling event {:?}", event);
+
+ match event.view() {
+ EventView::Seek(_) => false,
+ _ => pad.event_default(Some(element), event),
+ }
+ }
+
+ fn src_query(&self, pad: &gst::Pad, element: &gst::Element, query: &mut gst::QueryRef) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad, "Handling query {:?}", query);
+
+ match query.view_mut() {
+ QueryView::Seeking(mut q) => {
+ let format = q.get_format();
+ q.set(
+ false,
+ gst::GenericFormattedValue::Other(format, -1),
+ gst::GenericFormattedValue::Other(format, -1),
+ );
+ gst_log!(CAT, obj: pad, "Returning {:?}", q.get_mut_query());
+ true
+ }
+ QueryView::Duration(ref mut q) => {
+ use std::convert::TryInto;
+
+ if q.get_format() != gst::Format::Bytes {
+ return pad.query_default(Some(element), query);
+ }
+
+ /* First let's query the bytes duration upstream */
+ let mut peer_query = gst::query::Query::new_duration(gst::Format::Bytes);
+
+ if !self.sinkpad.peer_query(&mut peer_query) {
+ gst_error!(CAT, "Failed to query upstream duration");
+ return false;
+ }
+
+ let size = match peer_query.get_result().try_into().unwrap() {
+ gst::format::Bytes(Some(size)) => size,
+ gst::format::Bytes(None) => {
+ gst_error!(CAT, "Failed to query upstream duration");
+
+ return false;
+ }
+ };
+
+ let state = self.state.lock().unwrap();
+ let state = match state.as_ref() {
+ // If state isn't set, it means that the
+ // element hasn't been activated yet.
+ None => return false,
+ Some(s) => s,
+ };
+
+ // calculate the number of chunks that exist in the stream
+ let total_chunks = (size + state.block_size as u64 - 1) / state.block_size as u64;
+ // add the MAC of each block
+ let size = size + total_chunks * box_::MACBYTES as u64;
+
+ // add static offsets
+ let size = size + super::HEADERS_SIZE as u64;
+
+ gst_debug!(CAT, obj: pad, "Setting duration bytes: {}", size);
+ q.set(gst::format::Bytes::from(size));
+
+ true
+ }
+ _ => pad.query_default(Some(element), query),
+ }
+ }
+}
+
+impl ObjectSubclass for Encrypter {
+ const NAME: &'static str = "RsSodiumEncrypter";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ let templ = klass.get_pad_template("sink").unwrap();
+ let sinkpad = gst::Pad::new_from_template(&templ, Some("sink"));
+ let templ = klass.get_pad_template("src").unwrap();
+ let srcpad = gst::Pad::new_from_template(&templ, Some("src"));
+
+ Encrypter::set_pad_functions(&sinkpad, &srcpad);
+ let props = Mutex::new(Props::default());
+ let state = Mutex::new(None);
+
+ Self {
+ srcpad,
+ sinkpad,
+ props,
+ state,
+ }
+ }
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Encrypter",
+ "Generic",
+ "libsodium-based file encrypter",
+ "Jordan Petridis <jordan@centricular.com>",
+ );
+
+ let src_caps = gst::Caps::builder("application/x-sodium-encrypted").build();
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &src_caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &gst::Caps::new_any(),
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+ klass.install_properties(&PROPERTIES);
+ }
+}
+
+impl ObjectImpl for Encrypter {
+ glib_object_impl!();
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(&self.sinkpad).unwrap();
+ element.add_pad(&self.srcpad).unwrap();
+ }
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("sender-key", ..) => {
+ let mut props = self.props.lock().unwrap();
+ props.sender_key = value.get().expect("type checked upstream");
+ }
+
+ subclass::Property("receiver-key", ..) => {
+ let mut props = self.props.lock().unwrap();
+ props.receiver_key = value.get().expect("type checked upstream");
+ }
+
+ subclass::Property("block-size", ..) => {
+ let mut props = self.props.lock().unwrap();
+ props.block_size = value.get_some().expect("type checked upstream");
+ }
+
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("receiver-key", ..) => {
+ let props = self.props.lock().unwrap();
+ Ok(props.receiver_key.to_value())
+ }
+
+ subclass::Property("block-size", ..) => {
+ let props = self.props.lock().unwrap();
+ Ok(props.block_size.to_value())
+ }
+
+ _ => unimplemented!(),
+ }
+ }
+}
+
+impl ElementImpl for Encrypter {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_debug!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ let props = self.props.lock().unwrap().clone();
+
+ // Create an internal state struct from the provided properties or
+ // refuse to change state
+ let state_ = State::from_props(&props).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+
+ let mut state = self.state.lock().unwrap();
+ *state = Some(state_);
+ }
+ gst::StateChange::ReadyToNull => {
+ let _ = self.state.lock().unwrap().take();
+ }
+ _ => (),
+ }
+
+ let success = self.parent_change_state(element, transition)?;
+
+ if transition == gst::StateChange::ReadyToNull {
+ let _ = self.state.lock().unwrap().take();
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "sodiumencrypter",
+ gst::Rank::None,
+ Encrypter::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-sodium/src/lib.rs b/generic/gst-plugin-sodium/src/lib.rs
new file mode 100644
index 000000000..3961ddc7c
--- /dev/null
+++ b/generic/gst-plugin-sodium/src/lib.rs
@@ -0,0 +1,83 @@
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+#![crate_type = "cdylib"]
+
+#[macro_use]
+extern crate glib;
+#[macro_use]
+extern crate gst;
+#[macro_use]
+extern crate lazy_static;
+
+const TYPEFIND_HEADER: &[u8; 12] = b"gst-sodium10";
+// `core::slice::<impl [T]>::len` is not yet stable as a const fn
+// const TYPEFIND_HEADER_SIZE: usize = TYPEFIND_HEADER.len();
+const TYPEFIND_HEADER_SIZE: usize = 12;
+/// Encryted steams use an offset at the start to store the block_size
+/// and the nonce that was used.
+const HEADERS_SIZE: usize =
+ TYPEFIND_HEADER_SIZE + sodiumoxide::crypto::box_::NONCEBYTES + std::mem::size_of::<u32>();
+
+mod decrypter;
+mod encrypter;
+
+fn typefind_register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ use gst::{Caps, TypeFind, TypeFindProbability};
+
+ TypeFind::register(
+ Some(plugin),
+ "sodium_encrypted_typefind",
+ gst::Rank::None,
+ None,
+ Some(&Caps::new_simple("application/x-sodium-encrypted", &[])),
+ |typefind| {
+ if let Some(data) = typefind.peek(0, TYPEFIND_HEADER_SIZE as u32) {
+ if data == TYPEFIND_HEADER {
+ typefind.suggest(
+ TypeFindProbability::Maximum,
+ &Caps::new_simple("application/x-sodium-encrypted", &[]),
+ );
+ }
+ }
+ },
+ )
+}
+
+fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ encrypter::register(plugin)?;
+ decrypter::register(plugin)?;
+ typefind_register(plugin)?;
+ Ok(())
+}
+
+gst_plugin_define!(
+ sodium,
+ env!("CARGO_PKG_DESCRIPTION"),
+ plugin_init,
+ concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
+ "MIT/X11",
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_REPOSITORY"),
+ env!("BUILD_REL_DATE")
+);
diff --git a/generic/gst-plugin-sodium/tests/decrypter.rs b/generic/gst-plugin-sodium/tests/decrypter.rs
new file mode 100644
index 000000000..2fbb91e7a
--- /dev/null
+++ b/generic/gst-plugin-sodium/tests/decrypter.rs
@@ -0,0 +1,323 @@
+// decrypter.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate pretty_assertions;
+
+extern crate gstsodium;
+
+use glib::prelude::*;
+use gst::prelude::*;
+
+use std::sync::{Arc, Mutex};
+
+use std::path::PathBuf;
+
+lazy_static! {
+ static ref SENDER_PUBLIC: glib::Bytes = {
+ let public = [
+ 66, 248, 199, 74, 216, 55, 228, 116, 52, 17, 147, 56, 65, 130, 134, 148, 157, 153, 235,
+ 171, 179, 147, 120, 71, 100, 243, 133, 120, 160, 14, 111, 65,
+ ];
+ glib::Bytes::from_owned(public)
+ };
+ static ref RECEIVER_PRIVATE: glib::Bytes = {
+ let secret = [
+ 54, 221, 217, 54, 94, 235, 167, 2, 187, 249, 71, 31, 59, 27, 19, 166, 78, 236, 102, 48,
+ 29, 142, 41, 189, 22, 146, 218, 69, 147, 165, 240, 235,
+ ];
+ glib::Bytes::from_owned(secret)
+ };
+}
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstsodium::plugin_register_static().unwrap();
+ });
+}
+
+#[test]
+fn test_pipeline() {
+ init();
+
+ let pipeline = gst::Pipeline::new(Some("sodium-decrypter-test"));
+
+ let input_path = {
+ let mut r = PathBuf::new();
+ r.push(env!("CARGO_MANIFEST_DIR"));
+ r.push("tests");
+ r.push("encrypted_sample");
+ r.set_extension("enc");
+ r
+ };
+
+ let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
+ filesrc
+ .set_property("location", &input_path.to_str().unwrap())
+ .expect("failed to set property");
+
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("sender-key", &*SENDER_PUBLIC)
+ .expect("failed to set property");
+ dec.set_property("receiver-key", &*RECEIVER_PRIVATE)
+ .expect("failed to set property");
+
+ // the typefind element here is cause the decrypter only supports
+ // operating in pull mode bu the filesink wants push-mode.
+ let typefind = gst::ElementFactory::make("typefind", None).unwrap();
+ let sink = gst::ElementFactory::make("appsink", None).unwrap();
+
+ pipeline
+ .add_many(&[&filesrc, &dec, &typefind, &sink])
+ .expect("failed to add elements to the pipeline");
+ gst::Element::link_many(&[&filesrc, &dec, &typefind, &sink])
+ .expect("failed to link the elements");
+
+ let adapter = Arc::new(Mutex::new(gst_base::UniqueAdapter::new()));
+
+ let sink = sink.downcast::<gst_app::AppSink>().unwrap();
+ let adapter_clone = adapter.clone();
+ sink.set_callbacks(
+ gst_app::AppSinkCallbacks::new()
+ // Add a handler to the "new-sample" signal.
+ .new_sample(move |appsink| {
+ // Pull the sample in question out of the appsink's buffer.
+ let sample = appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?;
+ let buffer = sample.get_buffer().ok_or(gst::FlowError::Error)?;
+
+ let mut adapter = adapter_clone.lock().unwrap();
+ adapter.push(buffer.to_owned());
+
+ Ok(gst::FlowSuccess::Ok)
+ })
+ .build(),
+ );
+
+ pipeline
+ .set_state(gst::State::Playing)
+ .expect("Unable to set the pipeline to the `Playing` state");
+
+ let bus = pipeline.get_bus().unwrap();
+ for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Error(err) => {
+ eprintln!(
+ "Error received from element {:?}: {}",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error()
+ );
+ eprintln!("Debugging information: {:?}", err.get_debug());
+ unreachable!();
+ }
+ MessageView::Eos(..) => break,
+ _ => (),
+ }
+ }
+
+ pipeline
+ .set_state(gst::State::Null)
+ .expect("Unable to set the pipeline to the `Playing` state");
+
+ let expected_output = include_bytes!("sample.mp3");
+
+ let mut adapter = adapter.lock().unwrap();
+ let available = adapter.available();
+ assert_eq!(available, expected_output.len());
+ let output_buffer = adapter.take_buffer(available).unwrap();
+ let output = output_buffer.map_readable().unwrap();
+ assert_eq!(expected_output.as_ref(), output.as_ref());
+}
+
+#[test]
+fn test_pull_range() {
+ init();
+
+ let pipeline = gst::Pipeline::new(Some("sodium-decrypter-pull-range-test"));
+ let input_path = {
+ let mut r = PathBuf::new();
+ r.push(env!("CARGO_MANIFEST_DIR"));
+ r.push("tests");
+ r.push("encrypted_sample");
+ r.set_extension("enc");
+ r
+ };
+
+ let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
+ filesrc
+ .set_property("location", &input_path.to_str().unwrap())
+ .expect("failed to set property");
+
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("sender-key", &*SENDER_PUBLIC)
+ .expect("failed to set property");
+ dec.set_property("receiver-key", &*RECEIVER_PRIVATE)
+ .expect("failed to set property");
+
+ pipeline
+ .add_many(&[&filesrc, &dec])
+ .expect("failed to add elements to the pipeline");
+ gst::Element::link_many(&[&filesrc, &dec]).expect("failed to link the elements");
+
+ // Activate in the pad in pull mode
+ pipeline
+ .set_state(gst::State::Ready)
+ .expect("Unable to set the pipeline to the `Playing` state");
+ let srcpad = dec.get_static_pad("src").unwrap();
+ srcpad.activate_mode(gst::PadMode::Pull, true).unwrap();
+
+ pipeline
+ .set_state(gst::State::Playing)
+ .expect("Unable to set the pipeline to the `Playing` state");
+
+ // Test that the decryptor is seekable
+ let mut q = gst::query::Query::new_seeking(gst::Format::Bytes);
+ srcpad.query(&mut q);
+
+ // get the seeking capabilities
+ let (seekable, start, stop) = q.get_result();
+ assert_eq!(seekable, true);
+ assert_eq!(
+ start,
+ gst::GenericFormattedValue::Bytes(gst::format::Bytes(Some(0)))
+ );
+ assert_eq!(
+ stop,
+ gst::GenericFormattedValue::Bytes(gst::format::Bytes(Some(6043)))
+ );
+
+ // do pulls
+ let expected_array_1 = [
+ 255, 251, 192, 196, 0, 0, 13, 160, 37, 86, 116, 240, 0, 42, 73, 33, 43, 63, 61, 16, 128, 5,
+ 53, 37, 220, 28, 225, 35, 16, 243, 140, 220, 4, 192, 2, 64, 14, 3, 144, 203, 67, 208, 244,
+ 61, 70, 175, 103, 127, 28, 0,
+ ];
+ let buf1 = srcpad.get_range(0, 50).unwrap();
+ assert_eq!(buf1.get_size(), 50);
+ let map1 = buf1.map_readable().unwrap();
+ assert_eq!(&map1[..], &expected_array_1[..]);
+
+ let expected_array_2 = [
+ 255, 251, 192, 196, 0, 0, 13, 160, 37, 86, 116, 240, 0, 42, 73, 33, 43, 63, 61, 16, 128, 5,
+ 53, 37, 220, 28, 225, 35, 16, 243, 140, 220, 4, 192, 2, 64, 14, 3, 144, 203, 67, 208, 244,
+ 61, 70, 175, 103, 127, 28, 0, 0, 0, 0, 12, 60, 60, 60, 122, 0, 0, 0, 12, 195, 195, 195,
+ 207, 192, 0, 0, 3, 113, 195, 199, 255, 255, 254, 97, 225, 225, 231, 160, 0, 0, 49, 24, 120,
+ 120, 121, 232, 0, 0, 12, 252, 195, 195, 199, 128, 0, 0, 0,
+ ];
+ let buf2 = srcpad.get_range(0, 100).unwrap();
+ assert_eq!(buf2.get_size(), 100);
+ let map2 = buf2.map_readable().unwrap();
+ assert_eq!(&map2[..], &expected_array_2[..]);
+
+ // compare the first 50 bytes of the two slices
+ // they should match
+ assert_eq!(&map1[..], &map2[..map1.len()]);
+
+ // request in the middle of a block
+ let buf = srcpad.get_range(853, 100).unwrap();
+ // result size doesn't include the block macs,
+ assert_eq!(buf.get_size(), 100);
+
+ // read till eos, this also will pull multiple blocks
+ let buf = srcpad.get_range(853, 42000).unwrap();
+ // 6031 (size of file) - 883 (requersted offset) - headers size - (numbler of blcks * block mac)
+ assert_eq!(buf.get_size(), 5054);
+
+ // read 0 bytes from the start
+ let buf = srcpad.get_range(0, 0).unwrap();
+ assert_eq!(buf.get_size(), 0);
+
+ // read 0 bytes somewhere in the middle
+ let buf = srcpad.get_range(4242, 0).unwrap();
+ assert_eq!(buf.get_size(), 0);
+
+ // read 0 bytes to eos
+ let res = srcpad.get_range(6003, 0);
+ assert_eq!(res, Err(gst::FlowError::Eos));
+
+ // read 100 bytes at eos
+ let res = srcpad.get_range(6003, 100);
+ assert_eq!(res, Err(gst::FlowError::Eos));
+
+ // read 100 bytes way past eos
+ let res = srcpad.get_range(424_242, 100);
+ assert_eq!(res, Err(gst::FlowError::Eos));
+
+ // read 10 bytes at eos -1, should return a single byte
+ let buf = srcpad.get_range(5906, 10).unwrap();
+ assert_eq!(buf.get_size(), 1);
+
+ pipeline
+ .set_state(gst::State::Null)
+ .expect("Unable to set the pipeline to the `Playing` state");
+}
+
+#[test]
+fn test_state_changes() {
+ init();
+
+ // NullToReady without keys provided
+ {
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ assert!(dec.change_state(gst::StateChange::NullToReady).is_err());
+
+ // Set only receiver key
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("receiver-key", &*RECEIVER_PRIVATE)
+ .expect("failed to set property");
+ assert!(dec.change_state(gst::StateChange::NullToReady).is_err());
+
+ // Set only sender key
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("sender-key", &*SENDER_PUBLIC)
+ .expect("failed to set property");
+ assert!(dec.change_state(gst::StateChange::NullToReady).is_err());
+ }
+
+ // NullToReady, no nonce provided
+ {
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("sender-key", &*SENDER_PUBLIC)
+ .expect("failed to set property");
+ dec.set_property("receiver-key", &*RECEIVER_PRIVATE)
+ .expect("failed to set property");
+ assert!(dec.change_state(gst::StateChange::NullToReady).is_ok());
+ }
+
+ // ReadyToNull
+ {
+ let dec = gst::ElementFactory::make("sodiumdecrypter", None).unwrap();
+ dec.set_property("sender-key", &*SENDER_PUBLIC)
+ .expect("failed to set property");
+ dec.set_property("receiver-key", &*RECEIVER_PRIVATE)
+ .expect("failed to set property");
+ assert!(dec.change_state(gst::StateChange::NullToReady).is_ok());
+ }
+}
diff --git a/generic/gst-plugin-sodium/tests/encrypted_sample.enc b/generic/gst-plugin-sodium/tests/encrypted_sample.enc
new file mode 100644
index 000000000..f63b133a2
--- /dev/null
+++ b/generic/gst-plugin-sodium/tests/encrypted_sample.enc
Binary files differ
diff --git a/generic/gst-plugin-sodium/tests/encrypter.rs b/generic/gst-plugin-sodium/tests/encrypter.rs
new file mode 100644
index 000000000..e2f6dc83a
--- /dev/null
+++ b/generic/gst-plugin-sodium/tests/encrypter.rs
@@ -0,0 +1,152 @@
+// encrypter.rs
+//
+// Copyright 2019 Jordan Petridis <jordan@centricular.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// SPDX-License-Identifier: MIT
+
+#[macro_use]
+extern crate lazy_static;
+extern crate gstsodium;
+
+use glib::prelude::*;
+use gst::prelude::*;
+
+lazy_static! {
+ static ref RECEIVER_PUBLIC: glib::Bytes = {
+ let public = [
+ 28, 95, 33, 124, 28, 103, 80, 78, 7, 28, 234, 40, 226, 179, 253, 166, 169, 64, 78, 5,
+ 57, 92, 151, 179, 221, 89, 68, 70, 44, 225, 219, 19,
+ ];
+
+ glib::Bytes::from_owned(public)
+ };
+ static ref SENDER_PRIVATE: glib::Bytes = {
+ let secret = [
+ 154, 227, 90, 239, 206, 184, 202, 234, 176, 161, 14, 91, 218, 98, 142, 13, 145, 223,
+ 210, 222, 224, 240, 98, 51, 142, 165, 255, 1, 159, 100, 242, 162,
+ ];
+ glib::Bytes::from_owned(secret)
+ };
+ static ref NONCE: glib::Bytes = {
+ let nonce = [
+ 144, 187, 179, 230, 15, 4, 241, 15, 37, 133, 22, 30, 50, 106, 70, 159, 243, 218, 173,
+ 22, 18, 36, 4, 45,
+ ];
+ glib::Bytes::from_owned(nonce)
+ };
+}
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstsodium::plugin_register_static().unwrap();
+ // set the nonce
+ std::env::set_var("GST_SODIUM_ENCRYPT_NONCE", hex::encode(&*NONCE));
+ });
+}
+
+#[test]
+fn encrypt_file() {
+ init();
+
+ let input = include_bytes!("sample.mp3");
+ let expected_output = include_bytes!("encrypted_sample.enc");
+
+ let mut adapter = gst_base::UniqueAdapter::new();
+
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ enc.set_property("sender-key", &*SENDER_PRIVATE)
+ .expect("failed to set property");
+ enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
+ .expect("failed to set property");
+ enc.set_property("block-size", &1024u32)
+ .expect("failed to set property");
+
+ let mut h = gst_check::Harness::new_with_element(&enc, None, None);
+ h.add_element_src_pad(&enc.get_static_pad("src").expect("failed to get src pad"));
+ h.add_element_sink_pad(&enc.get_static_pad("sink").expect("failed to get src pad"));
+ h.set_src_caps_str("application/x-sodium-encrypted");
+
+ let buf = gst::Buffer::from_mut_slice(Vec::from(&input[..]));
+
+ assert_eq!(h.push(buf), Ok(gst::FlowSuccess::Ok));
+ h.push_event(gst::Event::new_eos().build());
+
+ println!("Pulling buffer...");
+ while let Ok(buf) = h.pull() {
+ adapter.push(buf);
+ if adapter.available() >= expected_output.len() {
+ break;
+ }
+ }
+
+ let buf = adapter
+ .take_buffer(adapter.available())
+ .expect("failed to take buffer");
+ let map = buf.map_readable().expect("Couldn't map buffer readable");
+ assert_eq!(map.as_ref(), expected_output.as_ref());
+}
+
+#[test]
+fn test_state_changes() {
+ init();
+
+ // NullToReady without keys provided
+ {
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ assert!(enc.change_state(gst::StateChange::NullToReady).is_err());
+
+ // Set only receiver key
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
+ .expect("failed to set property");
+ assert!(enc.change_state(gst::StateChange::NullToReady).is_err());
+
+ // Set only sender key
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ enc.set_property("sender-key", &*SENDER_PRIVATE)
+ .expect("failed to set property");
+ assert!(enc.change_state(gst::StateChange::NullToReady).is_err());
+ }
+
+ // NullToReady
+ {
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ enc.set_property("sender-key", &*SENDER_PRIVATE)
+ .expect("failed to set property");
+ enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
+ .expect("failed to set property");
+ assert!(enc.change_state(gst::StateChange::NullToReady).is_ok());
+ }
+
+ // ReadyToNull
+ {
+ let enc = gst::ElementFactory::make("sodiumencrypter", None).unwrap();
+ enc.set_property("sender-key", &*SENDER_PRIVATE)
+ .expect("failed to set property");
+ enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
+ .expect("failed to set property");
+ assert!(enc.change_state(gst::StateChange::NullToReady).is_ok());
+ }
+}
diff --git a/generic/gst-plugin-sodium/tests/sample.mp3 b/generic/gst-plugin-sodium/tests/sample.mp3
new file mode 100644
index 000000000..839e90d59
--- /dev/null
+++ b/generic/gst-plugin-sodium/tests/sample.mp3
Binary files differ
diff --git a/generic/gst-plugin-threadshare/Cargo.toml b/generic/gst-plugin-threadshare/Cargo.toml
new file mode 100644
index 000000000..3186d56a6
--- /dev/null
+++ b/generic/gst-plugin-threadshare/Cargo.toml
@@ -0,0 +1,53 @@
+[package]
+name = "gst-plugin-threadshare"
+version = "0.6.0"
+authors = ["Sebastian Dröge <sebastian@centricular.com>"]
+license = "LGPL-2.1+"
+description = "Threadshare Plugin"
+repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
+edition = "2018"
+
+[dependencies]
+libc = "0.2"
+glib-sys = { git = "https://github.com/gtk-rs/sys" }
+gobject-sys = { git = "https://github.com/gtk-rs/sys" }
+gio-sys = { git = "https://github.com/gtk-rs/sys" }
+glib = { git = "https://github.com/gtk-rs/glib" }
+gio = { git = "https://github.com/gtk-rs/gio" }
+gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features=["v1_10"] }
+gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
+gstreamer-sys = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs-sys" }
+pin-project = "0.4"
+tokio = { git = "https://github.com/fengalin/tokio", tag = "tokio-0.2.12-throttling", features = ["io-util", "macros", "rt-core", "sync", "stream", "time", "tcp", "udp", "rt-util"] }
+futures = "0.3"
+lazy_static = "1.0"
+rand = "0.7"
+net2 = "0.2"
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3", features = ["winsock2", "processthreadsapi"] }
+
+[lib]
+name = "gstthreadshare"
+crate-type = ["cdylib", "rlib"]
+path = "src/lib.rs"
+
+[[example]]
+name = "benchmark"
+path = "examples/benchmark.rs"
+
+[[example]]
+name = "udpsrc-benchmark-sender"
+path = "examples/udpsrc_benchmark_sender.rs"
+
+[[example]]
+name = "tcpclientsrc-benchmark-sender"
+path = "examples/tcpclientsrc_benchmark_sender.rs"
+
+[build-dependencies]
+gst-plugin-version-helper = { path="../../gst-plugin-version-helper" }
+cc = "1.0.38"
+pkg-config = "0.3.15"
diff --git a/generic/gst-plugin-threadshare/build.rs b/generic/gst-plugin-threadshare/build.rs
new file mode 100644
index 000000000..fd089efd5
--- /dev/null
+++ b/generic/gst-plugin-threadshare/build.rs
@@ -0,0 +1,35 @@
+extern crate cc;
+extern crate gst_plugin_version_helper;
+extern crate pkg_config;
+
+fn main() {
+ let gstreamer = pkg_config::probe_library("gstreamer-1.0").unwrap();
+ let gstrtp = pkg_config::probe_library("gstreamer-rtp-1.0").unwrap();
+ let includes = [gstreamer.include_paths, gstrtp.include_paths];
+
+ let mut build = cc::Build::new();
+
+ for p in includes.iter().flatten() {
+ build.include(p);
+ }
+
+ build.file("src/jitterbuffer/rtpjitterbuffer.c");
+ build.file("src/jitterbuffer/rtpstats.c");
+
+ build.define("RTPJitterBuffer", "TsRTPJitterBuffer");
+ build.define("RTPJitterBufferClass", "TsRTPJitterBufferClass");
+ build.define("RTPJitterBufferPrivate", "TsRTPJitterBufferClass");
+
+ build.compile("libthreadshare-c.a");
+
+ println!("cargo:rustc-link-lib=dylib=gstrtp-1.0");
+
+ for path in gstrtp.link_paths.iter() {
+ println!(
+ "cargo:rustc-link-search=native={}",
+ path.to_str().expect("library path doesn't exist")
+ );
+ }
+
+ gst_plugin_version_helper::get_info()
+}
diff --git a/generic/gst-plugin-threadshare/examples/benchmark.rs b/generic/gst-plugin-threadshare/examples/benchmark.rs
new file mode 100644
index 000000000..f7d164ed6
--- /dev/null
+++ b/generic/gst-plugin-threadshare/examples/benchmark.rs
@@ -0,0 +1,173 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib;
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+
+use std::env;
+
+fn main() {
+ gst::init().unwrap();
+
+ #[cfg(debug_assertions)]
+ {
+ use std::path::Path;
+
+ let mut path = Path::new("target/debug");
+ if !path.exists() {
+ path = Path::new("../target/debug");
+ }
+
+ gst::Registry::get().scan_path(path);
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ use std::path::Path;
+
+ let mut path = Path::new("target/release");
+ if !path.exists() {
+ path = Path::new("../target/release");
+ }
+
+ gst::Registry::get().scan_path(path);
+ }
+
+ let args = env::args().collect::<Vec<_>>();
+ assert_eq!(args.len(), 6);
+ let n_streams: u16 = args[1].parse().unwrap();
+ let source = &args[2];
+ let n_groups: u32 = args[3].parse().unwrap();
+ let wait: u32 = args[4].parse().unwrap();
+
+ let l = glib::MainLoop::new(None, false);
+ let pipeline = gst::Pipeline::new(None);
+
+ for i in 0..n_streams {
+ let sink =
+ gst::ElementFactory::make("fakesink", Some(format!("sink-{}", i).as_str())).unwrap();
+ sink.set_property("sync", &false).unwrap();
+ sink.set_property("async", &false).unwrap();
+
+ let source = match source.as_str() {
+ "udpsrc" => {
+ let source =
+ gst::ElementFactory::make("udpsrc", Some(format!("source-{}", i).as_str()))
+ .unwrap();
+ source
+ .set_property("port", &(40000i32 + (i as i32)))
+ .unwrap();
+ source
+ .set_property("retrieve-sender-address", &false)
+ .unwrap();
+
+ source
+ }
+ "ts-udpsrc" => {
+ let source =
+ gst::ElementFactory::make("ts-udpsrc", Some(format!("source-{}", i).as_str()))
+ .unwrap();
+ source
+ .set_property("port", &(40000u32 + (i as u32)))
+ .unwrap();
+ source
+ .set_property("context", &format!("context-{}", (i as u32) % n_groups))
+ .unwrap();
+ source.set_property("context-wait", &wait).unwrap();
+
+ source
+ }
+ "ts-tcpclientsrc" => {
+ let source = gst::ElementFactory::make(
+ "ts-tcpclientsrc",
+ Some(format!("source-{}", i).as_str()),
+ )
+ .unwrap();
+ source.set_property("port", &(40000u32)).unwrap();
+ source
+ .set_property("context", &format!("context-{}", (i as u32) % n_groups))
+ .unwrap();
+ source.set_property("context-wait", &wait).unwrap();
+
+ source
+ }
+ "tonegeneratesrc" => {
+ let source = gst::ElementFactory::make(
+ "tonegeneratesrc",
+ Some(format!("source-{}", i).as_str()),
+ )
+ .unwrap();
+ source
+ .set_property("samplesperbuffer", &((wait as i32) * 8000 / 1000))
+ .unwrap();
+
+ sink.set_property("sync", &true).unwrap();
+
+ source
+ }
+ "ts-tonesrc" => {
+ let source =
+ gst::ElementFactory::make("ts-tonesrc", Some(format!("source-{}", i).as_str()))
+ .unwrap();
+ source
+ .set_property("samples-per-buffer", &((wait as u32) * 8000 / 1000))
+ .unwrap();
+ source
+ .set_property("context", &format!("context-{}", (i as u32) % n_groups))
+ .unwrap();
+ source.set_property("context-wait", &wait).unwrap();
+
+ source
+ }
+ _ => unimplemented!(),
+ };
+
+ pipeline.add_many(&[&source, &sink]).unwrap();
+ source.link(&sink).unwrap();
+ }
+
+ let bus = pipeline.get_bus().unwrap();
+ let l_clone = l.clone();
+ bus.add_watch(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::Eos(..) => l_clone.quit(),
+ MessageView::Error(err) => {
+ println!(
+ "Error from {:?}: {} ({:?})",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error(),
+ err.get_debug()
+ );
+ l_clone.quit();
+ }
+ _ => (),
+ };
+
+ glib::Continue(true)
+ })
+ .expect("Failed to add bus watch");
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ println!("started");
+
+ l.run();
+}
diff --git a/generic/gst-plugin-threadshare/examples/tcpclientsrc_benchmark_sender.rs b/generic/gst-plugin-threadshare/examples/tcpclientsrc_benchmark_sender.rs
new file mode 100644
index 000000000..435ba2c2a
--- /dev/null
+++ b/generic/gst-plugin-threadshare/examples/tcpclientsrc_benchmark_sender.rs
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 LEE Dongjun <redongjun@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use std::io::Write;
+use std::sync::{Arc, Mutex};
+use std::{net, thread, time};
+
+fn main() {
+ let sockets: Arc<Mutex<Vec<net::TcpStream>>> = Arc::new(Mutex::new(vec![]));
+
+ let streams = sockets.clone();
+ let _handler = thread::spawn(move || {
+ let listener = net::TcpListener::bind("0.0.0.0:40000").unwrap();
+ for stream in listener.incoming() {
+ streams.lock().unwrap().push(stream.unwrap());
+ }
+ });
+
+ let buffer = [0; 160];
+ let wait = time::Duration::from_millis(20);
+
+ loop {
+ let now = time::Instant::now();
+
+ for mut socket in sockets.lock().unwrap().iter() {
+ let _ = socket.write(&buffer);
+ }
+
+ let elapsed = now.elapsed();
+ if elapsed < wait {
+ thread::sleep(wait - elapsed);
+ }
+ }
+}
diff --git a/generic/gst-plugin-threadshare/examples/udpsrc_benchmark_sender.rs b/generic/gst-plugin-threadshare/examples/udpsrc_benchmark_sender.rs
new file mode 100644
index 000000000..2993b4759
--- /dev/null
+++ b/generic/gst-plugin-threadshare/examples/udpsrc_benchmark_sender.rs
@@ -0,0 +1,51 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use std::net;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::{env, thread, time};
+
+fn main() {
+ let args = env::args().collect::<Vec<_>>();
+ assert_eq!(args.len(), 2);
+ let n_streams: u16 = args[1].parse().unwrap();
+
+ let buffer = [0; 160];
+ let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
+
+ let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let destinations = (40000..(40000 + n_streams))
+ .map(|port| SocketAddr::new(ipaddr, port))
+ .collect::<Vec<_>>();
+
+ let wait = time::Duration::from_millis(20);
+
+ thread::sleep(time::Duration::from_millis(1000));
+
+ loop {
+ let now = time::Instant::now();
+
+ for dest in &destinations {
+ socket.send_to(&buffer, dest).unwrap();
+ }
+
+ let elapsed = now.elapsed();
+ if elapsed < wait {
+ thread::sleep(wait - elapsed);
+ }
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/appsrc.rs b/generic/gst-plugin-threadshare/src/appsrc.rs
new file mode 100644
index 000000000..689b276bf
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/appsrc.rs
@@ -0,0 +1,794 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::channel::mpsc;
+use futures::lock::Mutex as FutMutex;
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_log, gst_trace};
+
+use lazy_static::lazy_static;
+
+use rand;
+
+use std::convert::TryInto;
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+use std::u32;
+
+use crate::runtime::prelude::*;
+use crate::runtime::{Context, PadSrc, PadSrcRef, Task};
+
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+const DEFAULT_CAPS: Option<gst::Caps> = None;
+const DEFAULT_MAX_BUFFERS: u32 = 10;
+const DEFAULT_DO_TIMESTAMP: bool = false;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ context: String,
+ context_wait: u32,
+ caps: Option<gst::Caps>,
+ max_buffers: u32,
+ do_timestamp: bool,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ caps: DEFAULT_CAPS,
+ max_buffers: DEFAULT_MAX_BUFFERS,
+ do_timestamp: DEFAULT_DO_TIMESTAMP,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 5] = [
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-buffers", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max Buffers",
+ "Maximum number of buffers to queue up",
+ 1,
+ u32::MAX,
+ DEFAULT_MAX_BUFFERS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("caps", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Caps",
+ "Caps to use",
+ gst::Caps::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("do-timestamp", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Do Timestamp",
+ "Timestamp buffers with the current running time on arrival",
+ DEFAULT_DO_TIMESTAMP,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-appsrc",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing app source"),
+ );
+}
+
+#[derive(Debug)]
+enum StreamItem {
+ Buffer(gst::Buffer),
+ Event(gst::Event),
+}
+
+#[derive(Debug)]
+struct AppSrcPadHandlerState {
+ need_initial_events: bool,
+ need_segment: bool,
+ caps: Option<gst::Caps>,
+}
+
+impl Default for AppSrcPadHandlerState {
+ fn default() -> Self {
+ AppSrcPadHandlerState {
+ need_initial_events: true,
+ need_segment: true,
+ caps: None,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct AppSrcPadHandlerInner {
+ state: FutMutex<AppSrcPadHandlerState>,
+ configured_caps: StdMutex<Option<gst::Caps>>,
+}
+
+#[derive(Clone, Debug, Default)]
+struct AppSrcPadHandler(Arc<AppSrcPadHandlerInner>);
+
+impl AppSrcPadHandler {
+ fn prepare(&self, caps: Option<gst::Caps>) {
+ self.0
+ .state
+ .try_lock()
+ .expect("State locked elsewhere")
+ .caps = caps;
+ }
+
+ fn reset(&self) {
+ *self.0.state.try_lock().expect("State locked elsewhere") = Default::default();
+ *self.0.configured_caps.lock().unwrap() = None;
+ }
+
+ fn set_need_segment(&self) {
+ self.0
+ .state
+ .try_lock()
+ .expect("State locked elsewhere")
+ .need_segment = true;
+ }
+
+ async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &gst::Element) {
+ let mut state = self.0.state.lock().await;
+ if state.need_initial_events {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Pushing initial events");
+
+ let stream_id = format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
+ let stream_start_evt = gst::Event::new_stream_start(&stream_id)
+ .group_id(gst::GroupId::next())
+ .build();
+ pad.push_event(stream_start_evt).await;
+
+ if let Some(ref caps) = state.caps {
+ let caps_evt = gst::Event::new_caps(&caps).build();
+ pad.push_event(caps_evt).await;
+ *self.0.configured_caps.lock().unwrap() = Some(caps.clone());
+ }
+
+ state.need_initial_events = false;
+ }
+
+ if state.need_segment {
+ let segment_evt =
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build();
+ pad.push_event(segment_evt).await;
+
+ state.need_segment = false;
+ }
+ }
+
+ async fn push_item(
+ self,
+ pad: &PadSrcRef<'_>,
+ element: &gst::Element,
+ item: StreamItem,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", item);
+
+ self.push_prelude(pad, element).await;
+
+ match item {
+ StreamItem::Buffer(buffer) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", buffer);
+ pad.push(buffer).await
+ }
+ StreamItem::Event(event) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ pad.push_event(event).await;
+ Ok(gst::FlowSuccess::Ok)
+ }
+ }
+ }
+}
+
+impl PadSrcHandler for AppSrcPadHandler {
+ type ElementImpl = AppSrc;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ appsrc: &AppSrc,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let ret = match event.view() {
+ EventView::FlushStart(..) => {
+ appsrc.flush_start(element);
+ true
+ }
+ EventView::FlushStop(..) => {
+ appsrc.flush_stop(element);
+ true
+ }
+ EventView::Reconfigure(..) => true,
+ EventView::Latency(..) => true,
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", event);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", event);
+ }
+
+ ret
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ _appsrc: &AppSrc,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+ let ret = match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ q.set(true, 0.into(), gst::CLOCK_TIME_NONE);
+ true
+ }
+ QueryView::Scheduling(ref mut q) => {
+ q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0);
+ q.add_scheduling_modes(&[gst::PadMode::Push]);
+ true
+ }
+ QueryView::Caps(ref mut q) => {
+ let caps = if let Some(caps) = self.0.configured_caps.lock().unwrap().as_ref() {
+ q.get_filter()
+ .map(|f| f.intersect_with_mode(caps, gst::CapsIntersectMode::First))
+ .unwrap_or_else(|| caps.clone())
+ } else {
+ q.get_filter()
+ .map(|f| f.to_owned())
+ .unwrap_or_else(gst::Caps::new_any)
+ };
+
+ q.set_result(&caps);
+
+ true
+ }
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", query);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", query);
+ }
+ ret
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum AppSrcState {
+ Paused,
+ RejectBuffers,
+ Started,
+}
+
+#[derive(Debug)]
+struct AppSrc {
+ src_pad: PadSrc,
+ src_pad_handler: AppSrcPadHandler,
+ task: Task,
+ state: StdMutex<AppSrcState>,
+ sender: StdMutex<Option<mpsc::Sender<StreamItem>>>,
+ receiver: StdMutex<Option<Arc<FutMutex<mpsc::Receiver<StreamItem>>>>>,
+ settings: StdMutex<Settings>,
+}
+
+impl AppSrc {
+ fn push_buffer(&self, element: &gst::Element, mut buffer: gst::Buffer) -> bool {
+ let state = self.state.lock().unwrap();
+ if *state == AppSrcState::RejectBuffers {
+ gst_debug!(CAT, obj: element, "Rejecting buffer due to pad state");
+ return false;
+ }
+
+ let do_timestamp = self.settings.lock().unwrap().do_timestamp;
+ if do_timestamp {
+ if let Some(clock) = element.get_clock() {
+ let base_time = element.get_base_time();
+ let now = clock.get_time();
+
+ let buffer = buffer.make_mut();
+ buffer.set_dts(now - base_time);
+ buffer.set_pts(gst::CLOCK_TIME_NONE);
+ } else {
+ gst_error!(CAT, obj: element, "Don't have a clock yet");
+ return false;
+ }
+ }
+
+ match self
+ .sender
+ .lock()
+ .unwrap()
+ .as_mut()
+ .unwrap()
+ .try_send(StreamItem::Buffer(buffer))
+ {
+ Ok(_) => true,
+ Err(err) => {
+ gst_error!(CAT, obj: element, "Failed to queue buffer: {}", err);
+ false
+ }
+ }
+ }
+
+ fn end_of_stream(&self, element: &gst::Element) -> bool {
+ let mut sender = self.sender.lock().unwrap();
+ let sender = match sender.as_mut() {
+ Some(sender) => sender,
+ None => return false,
+ };
+
+ let eos = StreamItem::Event(gst::Event::new_eos().build());
+ match sender.try_send(eos) {
+ Ok(_) => true,
+ Err(err) => {
+ gst_error!(CAT, obj: element, "Failed to queue EOS: {}", err);
+ false
+ }
+ }
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ let settings = self.settings.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ let context =
+ Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ let max_buffers = settings.max_buffers.try_into().map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Invalid max-buffers: {}, {}", settings.max_buffers, err]
+ )
+ })?;
+
+ let (sender, receiver) = mpsc::channel(max_buffers);
+ *self.sender.lock().unwrap() = Some(sender);
+ *self.receiver.lock().unwrap() = Some(Arc::new(FutMutex::new(receiver)));
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+ self.src_pad_handler.prepare(settings.caps.clone());
+ self.src_pad.prepare(&self.src_pad_handler);
+
+ gst_debug!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ *self.sender.lock().unwrap() = None;
+ *self.receiver.lock().unwrap() = None;
+
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Stopping");
+
+ self.flush(element);
+ self.src_pad_handler.reset();
+ *state = AppSrcState::RejectBuffers;
+
+ gst_debug!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn flush(&self, element: &gst::Element) {
+ gst_log!(CAT, obj: element, "Flushing");
+
+ self.task.stop();
+
+ let receiver = self.receiver.lock().unwrap();
+ let mut receiver = receiver
+ .as_ref()
+ .unwrap()
+ .try_lock()
+ .expect("receiver locked elsewhere");
+
+ // Purge the channel
+ loop {
+ match receiver.try_next() {
+ Ok(Some(_item)) => {
+ gst_log!(CAT, obj: element, "Dropping pending item");
+ }
+ Err(_) => {
+ gst_log!(CAT, obj: element, "No more pending item");
+ break;
+ }
+ Ok(None) => {
+ panic!("Channel sender dropped");
+ }
+ }
+ }
+
+ self.src_pad_handler.set_need_segment();
+
+ gst_log!(CAT, obj: element, "Flushed");
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ if *state == AppSrcState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return Ok(());
+ }
+
+ gst_debug!(CAT, obj: element, "Starting");
+
+ self.start_task(element);
+ *state = AppSrcState::Started;
+
+ gst_debug!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_task(&self, element: &gst::Element) {
+ let src_pad_handler = self.src_pad_handler.clone();
+ let pad_weak = self.src_pad.downgrade();
+ let element = element.clone();
+ let receiver = Arc::clone(self.receiver.lock().unwrap().as_ref().expect("No receiver"));
+
+ self.task.start(move || {
+ let src_pad_handler = src_pad_handler.clone();
+ let pad_weak = pad_weak.clone();
+ let element = element.clone();
+ let receiver = Arc::clone(&receiver);
+
+ async move {
+ let item = receiver.lock().await.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+
+ let item = match item {
+ Some(item) => item,
+ None => {
+ gst_log!(CAT, obj: pad.gst_pad(), "SrcPad channel aborted");
+ return glib::Continue(false);
+ }
+ };
+
+ match src_pad_handler.push_item(&pad, &element, item).await {
+ Ok(_) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Successfully pushed item");
+ glib::Continue(true)
+ }
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "EOS");
+ let eos = gst::Event::new_eos().build();
+ pad.push_event(eos).await;
+ glib::Continue(false)
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Flushing");
+ glib::Continue(false)
+ }
+ Err(err) => {
+ gst_error!(CAT, obj: pad.gst_pad(), "Got error {}", err);
+ gst_element_error!(
+ &element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ let mut state = self.state.lock().unwrap();
+ if *state == AppSrcState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(CAT, obj: element, "Stopping Flush");
+
+ self.flush(element);
+ self.start_task(element);
+ *state = AppSrcState::Started;
+
+ gst_debug!(CAT, obj: element, "Flush Stopped");
+ }
+
+ fn flush_start(&self, element: &gst::Element) {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Starting Flush");
+
+ self.task.cancel();
+ *state = AppSrcState::RejectBuffers;
+
+ gst_debug!(CAT, obj: element, "Flush Started");
+ }
+
+ fn pause(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Pausing");
+
+ self.task.pause();
+ *state = AppSrcState::Paused;
+
+ gst_debug!(CAT, obj: element, "Paused");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for AppSrc {
+ const NAME: &'static str = "RsTsAppSrc";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing app source",
+ "Source/Generic",
+ "Thread-sharing app source",
+ "Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+
+ klass.add_signal_with_class_handler(
+ "push-buffer",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[gst::Buffer::static_type()],
+ bool::static_type(),
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let buffer = args[1]
+ .get::<gst::Buffer>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let appsrc = Self::from_instance(&element);
+
+ Some(appsrc.push_buffer(&element, buffer).to_value())
+ },
+ );
+
+ klass.add_signal_with_class_handler(
+ "end-of-stream",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[],
+ bool::static_type(),
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let appsrc = Self::from_instance(&element);
+ Some(appsrc.end_of_stream(&element).to_value())
+ },
+ );
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ src_pad_handler: AppSrcPadHandler::default(),
+ task: Task::default(),
+ state: StdMutex::new(AppSrcState::RejectBuffers),
+ sender: StdMutex::new(None),
+ receiver: StdMutex::new(None),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for AppSrc {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("caps", ..) => {
+ settings.caps = value.get().expect("type checked upstream");
+ }
+ subclass::Property("max-buffers", ..) => {
+ settings.max_buffers = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("do-timestamp", ..) => {
+ settings.do_timestamp = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ subclass::Property("caps", ..) => Ok(settings.caps.to_value()),
+ subclass::Property("max-buffers", ..) => Ok(settings.max_buffers.to_value()),
+ subclass::Property("do-timestamp", ..) => Ok(settings.do_timestamp.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SOURCE);
+ }
+}
+
+impl ElementImpl for AppSrc {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ self.pause(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToPlaying => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-appsrc",
+ gst::Rank::None,
+ AppSrc::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/dataqueue.rs b/generic/gst-plugin-threadshare/src/dataqueue.rs
new file mode 100644
index 000000000..9f77d9664
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/dataqueue.rs
@@ -0,0 +1,282 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::{self, abortable, AbortHandle};
+
+use gst;
+use gst::gst_debug;
+use gst::prelude::*;
+
+use lazy_static::lazy_static;
+
+use std::collections::VecDeque;
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+use std::{u32, u64};
+
+lazy_static! {
+ static ref DATA_QUEUE_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-dataqueue",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing queue"),
+ );
+}
+
+#[derive(Debug)]
+pub enum DataQueueItem {
+ Buffer(gst::Buffer),
+ BufferList(gst::BufferList),
+ Event(gst::Event),
+}
+
+impl DataQueueItem {
+ fn size(&self) -> (u32, u32) {
+ match *self {
+ DataQueueItem::Buffer(ref buffer) => (1, buffer.get_size() as u32),
+ DataQueueItem::BufferList(ref list) => (
+ list.len() as u32,
+ list.iter().map(|b| b.get_size() as u32).sum::<u32>(),
+ ),
+ DataQueueItem::Event(_) => (0, 0),
+ }
+ }
+
+ fn timestamp(&self) -> Option<u64> {
+ match *self {
+ DataQueueItem::Buffer(ref buffer) => buffer.get_dts_or_pts().0,
+ DataQueueItem::BufferList(ref list) => {
+ list.iter().filter_map(|b| b.get_dts_or_pts().0).next()
+ }
+ DataQueueItem::Event(_) => None,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum DataQueueState {
+ Paused,
+ Started,
+ Stopped,
+}
+
+#[derive(Clone, Debug)]
+pub struct DataQueue(Arc<StdMutex<DataQueueInner>>);
+
+#[derive(Debug)]
+struct DataQueueInner {
+ element: gst::Element,
+ src_pad: gst::Pad,
+
+ state: DataQueueState,
+ queue: VecDeque<DataQueueItem>,
+
+ cur_size_buffers: u32,
+ cur_size_bytes: u32,
+ max_size_buffers: Option<u32>,
+ max_size_bytes: Option<u32>,
+ max_size_time: Option<u64>,
+
+ pending_handle: Option<AbortHandle>,
+}
+
+impl DataQueueInner {
+ fn wake(&mut self) {
+ if let Some(pending_handle) = self.pending_handle.take() {
+ pending_handle.abort();
+ }
+ }
+}
+
+impl DataQueue {
+ pub fn new(
+ element: &gst::Element,
+ src_pad: &gst::Pad,
+ max_size_buffers: Option<u32>,
+ max_size_bytes: Option<u32>,
+ max_size_time: Option<u64>,
+ ) -> DataQueue {
+ DataQueue(Arc::new(StdMutex::new(DataQueueInner {
+ element: element.clone(),
+ src_pad: src_pad.clone(),
+ state: DataQueueState::Stopped,
+ queue: VecDeque::new(),
+ cur_size_buffers: 0,
+ cur_size_bytes: 0,
+ max_size_buffers,
+ max_size_bytes,
+ max_size_time,
+ pending_handle: None,
+ })))
+ }
+
+ pub fn state(&self) -> DataQueueState {
+ self.0.lock().unwrap().state
+ }
+
+ pub fn start(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state == DataQueueState::Started {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue already Started");
+ return;
+ }
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Starting data queue");
+ inner.state = DataQueueState::Started;
+ inner.wake();
+ }
+
+ pub fn pause(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state == DataQueueState::Paused {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue already Paused");
+ return;
+ }
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Pausing data queue");
+ assert_eq!(DataQueueState::Started, inner.state);
+ inner.state = DataQueueState::Paused;
+ inner.wake();
+ }
+
+ pub fn stop(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state == DataQueueState::Stopped {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue already Stopped");
+ return;
+ }
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Stopping data queue");
+ inner.state = DataQueueState::Stopped;
+ inner.wake();
+ }
+
+ pub fn clear(&self) {
+ let mut inner = self.0.lock().unwrap();
+
+ assert_eq!(inner.state, DataQueueState::Paused);
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Clearing data queue");
+
+ let src_pad = inner.src_pad.clone();
+ for item in inner.queue.drain(..) {
+ if let DataQueueItem::Event(event) = item {
+ if event.is_sticky()
+ && event.get_type() != gst::EventType::Segment
+ && event.get_type() != gst::EventType::Eos
+ {
+ let _ = src_pad.store_sticky_event(&event);
+ }
+ }
+ }
+
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue cleared");
+ }
+
+ pub fn push(&self, item: DataQueueItem) -> Result<(), DataQueueItem> {
+ let mut inner = self.0.lock().unwrap();
+
+ if inner.state == DataQueueState::Stopped {
+ gst_debug!(
+ DATA_QUEUE_CAT,
+ obj: &inner.element,
+ "Rejecting item {:?} in state {:?}",
+ item,
+ inner.state
+ );
+ return Err(item);
+ }
+
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Pushing item {:?}", item);
+
+ let (count, bytes) = item.size();
+ let queue_ts = inner.queue.iter().filter_map(|i| i.timestamp()).next();
+ let ts = item.timestamp();
+
+ if let Some(max) = inner.max_size_buffers {
+ if max <= inner.cur_size_buffers {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Queue is full (buffers): {} <= {}", max, inner.cur_size_buffers);
+ return Err(item);
+ }
+ }
+
+ if let Some(max) = inner.max_size_bytes {
+ if max <= inner.cur_size_bytes {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Queue is full (bytes): {} <= {}", max, inner.cur_size_bytes);
+ return Err(item);
+ }
+ }
+
+ // FIXME: Use running time
+ if let (Some(max), Some(queue_ts), Some(ts)) = (inner.max_size_time, queue_ts, ts) {
+ let level = if queue_ts > ts {
+ queue_ts - ts
+ } else {
+ ts - queue_ts
+ };
+
+ if max <= level {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Queue is full (time): {} <= {}", max, level);
+ return Err(item);
+ }
+ }
+
+ inner.queue.push_back(item);
+ inner.cur_size_buffers += count;
+ inner.cur_size_bytes += bytes;
+
+ inner.wake();
+
+ Ok(())
+ }
+
+ // TODO: implement as a Stream now that we use a StdMutex
+ #[allow(clippy::should_implement_trait)]
+ pub async fn next(&mut self) -> Option<DataQueueItem> {
+ loop {
+ let pending_fut = {
+ let mut inner = self.0.lock().unwrap();
+ match inner.state {
+ DataQueueState::Started => match inner.queue.pop_front() {
+ None => {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue is empty");
+ }
+ Some(item) => {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Popped item {:?}", item);
+
+ let (count, bytes) = item.size();
+ inner.cur_size_buffers -= count;
+ inner.cur_size_bytes -= bytes;
+
+ return Some(item);
+ }
+ },
+ DataQueueState::Paused => {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue Paused");
+ return None;
+ }
+ DataQueueState::Stopped => {
+ gst_debug!(DATA_QUEUE_CAT, obj: &inner.element, "Data queue Stopped");
+ return None;
+ }
+ }
+
+ let (pending_fut, abort_handle) = abortable(future::pending::<()>());
+ inner.pending_handle = Some(abort_handle);
+
+ pending_fut
+ };
+
+ let _ = pending_fut.await;
+ }
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/inputselector.rs b/generic/gst-plugin-threadshare/src/inputselector.rs
new file mode 100644
index 000000000..47e3ba6f3
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/inputselector.rs
@@ -0,0 +1,684 @@
+// Copyright (C) 2020 Mathieu Duponchelle <mathieu@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::BoxFuture;
+use futures::future::{abortable, AbortHandle};
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_log, gst_trace};
+
+use lazy_static::lazy_static;
+
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+use std::u32;
+
+use crate::get_current_running_time;
+use crate::runtime::prelude::*;
+use crate::runtime::{self, PadSink, PadSinkRef, PadSrc, PadSrcRef};
+
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ context: String,
+ context_wait: u32,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 3] = [
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("active-pad", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Active Pad",
+ "Currently active pad",
+ gst::Pad::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Debug)]
+struct InputSelectorPadSinkHandlerInner {
+ segment: Option<gst::Segment>,
+ send_sticky: bool,
+ abort_handle: Option<AbortHandle>,
+}
+
+impl Default for InputSelectorPadSinkHandlerInner {
+ fn default() -> Self {
+ InputSelectorPadSinkHandlerInner {
+ segment: None,
+ send_sticky: true,
+ abort_handle: None,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct InputSelectorPadSinkHandler(Arc<Mutex<InputSelectorPadSinkHandlerInner>>);
+
+impl InputSelectorPadSinkHandler {
+ fn new() -> Self {
+ InputSelectorPadSinkHandler(Arc::new(Mutex::new(
+ InputSelectorPadSinkHandlerInner::default(),
+ )))
+ }
+
+ /* Wait until specified time */
+ async fn sync(&self, element: &gst::Element, running_time: gst::ClockTime) {
+ let now = get_current_running_time(&element);
+
+ if now.is_some() && now < running_time {
+ let delay = running_time - now;
+ runtime::time::delay_for(Duration::from_nanos(delay.nseconds().unwrap())).await;
+ }
+ }
+
+ async fn handle_item(
+ &self,
+ pad: &PadSinkRef<'_>,
+ element: &gst::Element,
+ mut buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let inputselector = InputSelector::from_instance(element);
+
+ let (stickies, is_active, sync_future, switched_pad) = {
+ let mut state = inputselector.state.lock().unwrap();
+ let mut inner = self.0.lock().unwrap();
+ let mut stickies = vec![];
+ let mut sync_future = None;
+ let switched_pad = state.switched_pad;
+
+ if let Some(segment) = &inner.segment {
+ if let Some(segment) = segment.downcast_ref::<gst::format::Time>() {
+ let rtime = segment.to_running_time(buffer.get_pts());
+ let (sync_fut, abort_handle) = abortable(self.sync(&element, rtime));
+ inner.abort_handle = Some(abort_handle);
+ sync_future = Some(sync_fut.map_err(|_| gst::FlowError::Flushing));
+ }
+ }
+
+ let is_active = {
+ if state.active_sinkpad.as_ref() == Some(pad.gst_pad()) {
+ if inner.send_sticky || state.switched_pad {
+ pad.gst_pad().sticky_events_foreach(|event| {
+ stickies.push(event.clone());
+ Ok(Some(event))
+ });
+
+ inner.send_sticky = false;
+ state.switched_pad = false;
+ }
+ true
+ } else {
+ false
+ }
+ };
+
+ (stickies, is_active, sync_future, switched_pad)
+ };
+
+ if let Some(sync_fut) = sync_future {
+ sync_fut.await?;
+ }
+
+ for event in stickies {
+ inputselector.src_pad.push_event(event).await;
+ }
+
+ if is_active {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", buffer);
+
+ if switched_pad && !buffer.get_flags().contains(gst::BufferFlags::DISCONT) {
+ let buffer = buffer.make_mut();
+ buffer.set_flags(gst::BufferFlags::DISCONT);
+ }
+
+ inputselector.src_pad.push(buffer).await
+ } else {
+ Ok(gst::FlowSuccess::Ok)
+ }
+ }
+}
+
+impl PadSinkHandler for InputSelectorPadSinkHandler {
+ type ElementImpl = InputSelector;
+
+ fn sink_chain(
+ &self,
+ pad: &PadSinkRef,
+ _inputselector: &InputSelector,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let this = self.clone();
+ let element = element.clone();
+ let pad_weak = pad.downgrade();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ this.handle_item(&pad, &element, buffer).await
+ }
+ .boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ pad: &PadSinkRef,
+ _inputselector: &InputSelector,
+ element: &gst::Element,
+ list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let this = self.clone();
+ let element = element.clone();
+ let pad_weak = pad.downgrade();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling buffer list {:?}", list);
+ // TODO: Ideally we would keep the list intact and forward it in one go
+ for buffer in list.iter_owned() {
+ this.handle_item(&pad, &element, buffer).await?;
+ }
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+ .boxed()
+ }
+
+ fn sink_event_serialized(
+ &self,
+ _pad: &PadSinkRef,
+ _inputselector: &InputSelector,
+ _element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ let this = self.clone();
+
+ async move {
+ let mut inner = this.0.lock().unwrap();
+
+ // Remember the segment for later use
+ if let gst::EventView::Segment(e) = event.view() {
+ inner.segment = Some(e.get_segment().clone());
+ }
+
+ // We sent sticky events together with the next buffer once it becomes
+ // the active pad.
+ //
+ // TODO: Other serialized events for the active pad can also be forwarded
+ // here, and sticky events could be forwarded directly. Needs forwarding of
+ // all other sticky events first!
+ if event.is_sticky() {
+ inner.send_sticky = true;
+ true
+ } else {
+ true
+ }
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ _pad: &PadSinkRef,
+ inputselector: &InputSelector,
+ _element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ /* Drop all events for now */
+ if let gst::EventView::FlushStart(..) = event.view() {
+ /* Unblock downstream */
+ inputselector.src_pad.gst_pad().push_event(event.clone());
+
+ let mut inner = self.0.lock().unwrap();
+
+ if let Some(abort_handle) = inner.abort_handle.take() {
+ abort_handle.abort();
+ }
+ }
+ true
+ }
+
+ fn sink_query(
+ &self,
+ pad: &PadSinkRef,
+ inputselector: &InputSelector,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling query {:?}", query);
+
+ if query.is_serialized() {
+ // FIXME: How can we do this (drops ALLOCATION and DRAIN)?
+ gst_log!(CAT, obj: pad.gst_pad(), "Dropping serialized query {:?}", query);
+ false
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding query {:?}", query);
+ inputselector.src_pad.gst_pad().peer_query(query)
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct InputSelectorPadSrcHandler;
+
+impl InputSelectorPadSrcHandler {}
+
+impl PadSrcHandler for InputSelectorPadSrcHandler {
+ type ElementImpl = InputSelector;
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ inputselector: &InputSelector,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+
+ match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ let mut ret = true;
+ let mut min_latency = 0.into();
+ let mut max_latency = gst::CLOCK_TIME_NONE;
+ let pads = {
+ let pads = inputselector.pads.lock().unwrap();
+ pads.sink_pads
+ .iter()
+ .map(|p| p.0.clone())
+ .collect::<Vec<_>>()
+ };
+
+ for pad in pads {
+ let mut peer_query = gst::query::Query::new_latency();
+
+ ret = pad.peer_query(&mut peer_query);
+
+ if ret {
+ let (live, min, max) = peer_query.get_result();
+ if live {
+ min_latency = std::cmp::max(min, min_latency);
+ if max_latency.is_none() && max.is_some() {
+ max_latency = max;
+ } else if max_latency.is_some() && max.is_some() {
+ max_latency = std::cmp::min(max, max_latency);
+ }
+ }
+ }
+ }
+
+ q.set(true, min_latency, max_latency);
+
+ ret
+ }
+ _ => {
+ let sinkpad = {
+ let state = inputselector.state.lock().unwrap();
+ state.active_sinkpad.clone()
+ };
+
+ if let Some(sinkpad) = sinkpad {
+ sinkpad.peer_query(query)
+ } else {
+ true
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+struct State {
+ active_sinkpad: Option<gst::Pad>,
+ switched_pad: bool,
+}
+
+impl Default for State {
+ fn default() -> State {
+ State {
+ active_sinkpad: None,
+ switched_pad: true,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Pads {
+ pad_serial: u32,
+ sink_pads: HashMap<gst::Pad, PadSink>,
+}
+
+impl Default for Pads {
+ fn default() -> Pads {
+ Pads {
+ pad_serial: 0,
+ sink_pads: HashMap::new(),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct InputSelector {
+ src_pad: PadSrc,
+ state: Mutex<State>,
+ settings: Mutex<Settings>,
+ pads: Mutex<Pads>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-input-selector",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing input selector"),
+ );
+}
+
+impl InputSelector {
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ self.src_pad.prepare(&InputSelectorPadSrcHandler);
+
+ let pads = self.pads.lock().unwrap();
+ for pad in pads.sink_pads.values() {
+ pad.prepare(&InputSelectorPadSinkHandler::new());
+ }
+
+ gst_debug!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ self.src_pad.unprepare();
+
+ let pads = self.pads.lock().unwrap();
+ for pad in pads.sink_pads.values() {
+ pad.unprepare();
+ }
+
+ *state = State::default();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for InputSelector {
+ const NAME: &'static str = "RsTsInputSelector";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing input selector",
+ "Generic",
+ "Simple input selector element",
+ "Mathieu Duponchelle <mathieu@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink_%u",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Request,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ state: Mutex::new(State::default()),
+ settings: Mutex::new(Settings::default()),
+ pads: Mutex::new(Pads::default()),
+ }
+ }
+}
+
+impl ObjectImpl for InputSelector {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("context", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("active-pad", ..) => {
+ let pad = value.get::<gst::Pad>().expect("type checked upstream");
+ let mut state = self.state.lock().unwrap();
+ let pads = self.pads.lock().unwrap();
+ let mut old_pad = None;
+ if let Some(ref pad) = pad {
+ if pads.sink_pads.get(&pad).is_some() {
+ old_pad = state.active_sinkpad.clone();
+ state.active_sinkpad = Some(pad.clone());
+ state.switched_pad = true;
+ }
+ } else {
+ state.active_sinkpad = None;
+ }
+
+ drop(pads);
+ drop(state);
+
+ if let Some(old_pad) = old_pad {
+ if Some(&old_pad) != pad.as_ref() {
+ let _ = old_pad.push_event(gst::Event::new_reconfigure().build());
+ }
+ }
+
+ if let Some(pad) = pad {
+ let _ = pad.push_event(gst::Event::new_reconfigure().build());
+ }
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("context", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.context.to_value())
+ }
+ subclass::Property("context-wait", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.context_wait.to_value())
+ }
+ subclass::Property("active-pad", ..) => {
+ let state = self.state.lock().unwrap();
+ let active_pad = state.active_sinkpad.clone();
+ Ok(active_pad.to_value())
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+ }
+}
+
+impl ElementImpl for InputSelector {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+
+ fn request_new_pad(
+ &self,
+ element: &gst::Element,
+ templ: &gst::PadTemplate,
+ _name: Option<String>,
+ _caps: Option<&gst::Caps>,
+ ) -> Option<gst::Pad> {
+ let mut state = self.state.lock().unwrap();
+ let mut pads = self.pads.lock().unwrap();
+ let sink_pad =
+ gst::Pad::new_from_template(&templ, Some(format!("sink_{}", pads.pad_serial).as_str()));
+ pads.pad_serial += 1;
+ sink_pad.set_active(true).unwrap();
+ element.add_pad(&sink_pad).unwrap();
+ let sink_pad = PadSink::new(sink_pad);
+ let ret = sink_pad.gst_pad().clone();
+
+ sink_pad.prepare(&InputSelectorPadSinkHandler::new());
+
+ if state.active_sinkpad.is_none() {
+ state.active_sinkpad = Some(ret.clone());
+ state.switched_pad = true;
+ }
+
+ pads.sink_pads.insert(ret.clone(), sink_pad);
+ drop(pads);
+ drop(state);
+
+ let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build());
+
+ Some(ret)
+ }
+
+ fn release_pad(&self, element: &gst::Element, pad: &gst::Pad) {
+ let mut pads = self.pads.lock().unwrap();
+ let sink_pad = pads.sink_pads.remove(pad).unwrap();
+ sink_pad.unprepare();
+ element.remove_pad(pad).unwrap();
+ drop(pads);
+
+ let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build());
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-input-selector",
+ gst::Rank::None,
+ InputSelector::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/jitterbuffer.rs b/generic/gst-plugin-threadshare/src/jitterbuffer/jitterbuffer.rs
new file mode 100644
index 000000000..6e2a2244e
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/jitterbuffer.rs
@@ -0,0 +1,1584 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::BoxFuture;
+use futures::future::{abortable, AbortHandle, Aborted};
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_error, gst_error_msg, gst_info, gst_log, gst_trace};
+use gst_rtp::RTPBuffer;
+
+use lazy_static::lazy_static;
+
+use std::cmp::{max, min, Ordering};
+use std::collections::{BTreeSet, VecDeque};
+use std::mem;
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+use std::time::Duration;
+
+use crate::get_current_running_time;
+use crate::runtime::prelude::*;
+use crate::runtime::{self, Context, PadSink, PadSinkRef, PadSrc, PadSrcRef, Task};
+
+use super::{RTPJitterBuffer, RTPJitterBufferItem, RTPPacketRateCtx};
+
+const DEFAULT_LATENCY_MS: u32 = 200;
+const DEFAULT_DO_LOST: bool = false;
+const DEFAULT_MAX_DROPOUT_TIME: u32 = 60000;
+const DEFAULT_MAX_MISORDER_TIME: u32 = 2000;
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ latency_ms: u32,
+ do_lost: bool,
+ max_dropout_time: u32,
+ max_misorder_time: u32,
+ context: String,
+ context_wait: u32,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ latency_ms: DEFAULT_LATENCY_MS,
+ do_lost: DEFAULT_DO_LOST,
+ max_dropout_time: DEFAULT_MAX_DROPOUT_TIME,
+ max_misorder_time: DEFAULT_MAX_MISORDER_TIME,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 7] = [
+ subclass::Property("latency", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Buffer latency in ms",
+ "Amount of ms to buffer",
+ 0,
+ std::u32::MAX,
+ DEFAULT_LATENCY_MS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("do-lost", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Do Lost",
+ "Send an event downstream when a packet is lost",
+ DEFAULT_DO_LOST,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-dropout-time", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max dropout time",
+ "The maximum time (milliseconds) of missing packets tolerated.",
+ 0,
+ std::u32::MAX,
+ DEFAULT_MAX_DROPOUT_TIME,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-misorder-time", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max misorder time",
+ "The maximum time (milliseconds) of misordered packets tolerated.",
+ 0,
+ std::u32::MAX,
+ DEFAULT_MAX_MISORDER_TIME,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("stats", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Statistics",
+ "Various statistics",
+ gst::Structure::static_type(),
+ glib::ParamFlags::READABLE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Eq)]
+struct GapPacket {
+ buffer: gst::Buffer,
+ seq: u16,
+ pt: u8,
+}
+
+impl GapPacket {
+ fn new(buffer: gst::Buffer) -> Self {
+ let mut rtp_buffer = RTPBuffer::from_buffer_readable(&buffer).unwrap();
+ let seq = rtp_buffer.get_seq();
+ let pt = rtp_buffer.get_payload_type();
+ drop(rtp_buffer);
+
+ Self { buffer, seq, pt }
+ }
+}
+
+impl Ord for GapPacket {
+ fn cmp(&self, other: &Self) -> Ordering {
+ 0.cmp(&gst_rtp::compare_seqnum(self.seq, other.seq))
+ }
+}
+
+impl PartialOrd for GapPacket {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl PartialEq for GapPacket {
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+struct SinkHandlerInner {
+ packet_rate_ctx: RTPPacketRateCtx,
+ ips_rtptime: Option<u32>,
+ ips_pts: gst::ClockTime,
+
+ gap_packets: BTreeSet<GapPacket>,
+
+ last_pt: Option<u8>,
+
+ last_in_seqnum: Option<u16>,
+ last_rtptime: Option<u32>,
+}
+
+impl Default for SinkHandlerInner {
+ fn default() -> Self {
+ SinkHandlerInner {
+ packet_rate_ctx: RTPPacketRateCtx::new(),
+ ips_rtptime: None,
+ ips_pts: gst::CLOCK_TIME_NONE,
+ gap_packets: BTreeSet::new(),
+ last_pt: None,
+ last_in_seqnum: None,
+ last_rtptime: None,
+ }
+ }
+}
+
+#[derive(Clone)]
+struct SinkHandler(Arc<StdMutex<SinkHandlerInner>>);
+
+impl SinkHandler {
+ fn new() -> Self {
+ SinkHandler(Arc::new(StdMutex::new(SinkHandlerInner::default())))
+ }
+
+ fn clear(&self) {
+ let mut inner = self.0.lock().unwrap();
+ *inner = SinkHandlerInner::default();
+ }
+
+ // For resetting if seqnum discontinuities
+ fn reset(
+ &self,
+ inner: &mut SinkHandlerInner,
+ state: &mut State,
+ element: &gst::Element,
+ ) -> BTreeSet<GapPacket> {
+ gst_info!(CAT, obj: element, "Resetting");
+
+ state.jbuf.borrow().flush();
+ state.jbuf.borrow().reset_skew();
+ state.discont = true;
+
+ state.last_popped_seqnum = None;
+ state.last_popped_pts = gst::CLOCK_TIME_NONE;
+
+ inner.last_in_seqnum = None;
+ inner.last_rtptime = None;
+
+ state.earliest_pts = gst::CLOCK_TIME_NONE;
+ state.earliest_seqnum = None;
+
+ inner.ips_rtptime = None;
+ inner.ips_pts = gst::CLOCK_TIME_NONE;
+
+ mem::replace(&mut inner.gap_packets, BTreeSet::new())
+ }
+
+ fn parse_caps(
+ &self,
+ inner: &mut SinkHandlerInner,
+ state: &mut State,
+ element: &gst::Element,
+ caps: &gst::Caps,
+ pt: u8,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let s = caps.get_structure(0).ok_or(gst::FlowError::Error)?;
+
+ gst_info!(CAT, obj: element, "Parsing {:?}", caps);
+
+ let payload = s
+ .get_some::<i32>("payload")
+ .map_err(|_| gst::FlowError::Error)?;
+
+ if pt != 0 && payload as u8 != pt {
+ return Err(gst::FlowError::Error);
+ }
+
+ inner.last_pt = Some(pt);
+ let clock_rate = s
+ .get_some::<i32>("clock-rate")
+ .map_err(|_| gst::FlowError::Error)?;
+
+ if clock_rate <= 0 {
+ return Err(gst::FlowError::Error);
+ }
+ state.clock_rate = Some(clock_rate as u32);
+
+ inner.packet_rate_ctx.reset(clock_rate);
+ state.jbuf.borrow().set_clock_rate(clock_rate as u32);
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+
+ fn calculate_packet_spacing(
+ &self,
+ inner: &mut SinkHandlerInner,
+ state: &mut State,
+ rtptime: u32,
+ pts: gst::ClockTime,
+ ) {
+ if inner.ips_rtptime != Some(rtptime) {
+ if inner.ips_pts.is_some() && pts.is_some() {
+ let new_packet_spacing = pts - inner.ips_pts;
+ let old_packet_spacing = state.packet_spacing;
+
+ if old_packet_spacing > new_packet_spacing {
+ state.packet_spacing = (new_packet_spacing + 3 * old_packet_spacing) / 4;
+ } else if old_packet_spacing > gst::ClockTime(Some(0)) {
+ state.packet_spacing = (3 * new_packet_spacing + old_packet_spacing) / 4;
+ } else {
+ state.packet_spacing = new_packet_spacing;
+ }
+
+ gst_debug!(
+ CAT,
+ "new packet spacing {}, old packet spacing {} combined to {}",
+ new_packet_spacing,
+ old_packet_spacing,
+ state.packet_spacing
+ );
+ }
+ inner.ips_rtptime = Some(rtptime);
+ inner.ips_pts = pts;
+ }
+ }
+
+ fn handle_big_gap_buffer(
+ &self,
+ inner: &mut SinkHandlerInner,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ pt: u8,
+ ) -> bool {
+ let gap_packets_length = inner.gap_packets.len();
+ let mut reset = false;
+
+ gst_debug!(
+ CAT,
+ obj: element,
+ "Handling big gap, gap packets length: {}",
+ gap_packets_length
+ );
+
+ inner.gap_packets.insert(GapPacket::new(buffer));
+
+ if gap_packets_length > 0 {
+ let mut prev_gap_seq = std::u32::MAX;
+ let mut all_consecutive = true;
+
+ for gap_packet in inner.gap_packets.iter() {
+ gst_log!(
+ CAT,
+ obj: element,
+ "Looking at gap packet with seq {}",
+ gap_packet.seq,
+ );
+
+ all_consecutive = gap_packet.pt == pt;
+
+ if prev_gap_seq == std::u32::MAX {
+ prev_gap_seq = gap_packet.seq as u32;
+ } else if gst_rtp::compare_seqnum(gap_packet.seq, prev_gap_seq as u16) != -1 {
+ all_consecutive = false;
+ } else {
+ prev_gap_seq = gap_packet.seq as u32;
+ }
+
+ if !all_consecutive {
+ break;
+ }
+ }
+
+ gst_debug!(CAT, obj: element, "all consecutive: {}", all_consecutive);
+
+ if all_consecutive && gap_packets_length > 3 {
+ reset = true;
+ } else if !all_consecutive {
+ inner.gap_packets.clear();
+ }
+ }
+
+ reset
+ }
+
+ fn store(
+ &self,
+ inner: &mut SinkHandlerInner,
+ pad: &gst::Pad,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let jb = JitterBuffer::from_instance(element);
+ let mut state = jb.state.lock().unwrap();
+
+ let (max_misorder_time, max_dropout_time) = {
+ let settings = jb.settings.lock().unwrap();
+ (settings.max_misorder_time, settings.max_dropout_time)
+ };
+
+ let (seq, rtptime, pt) = {
+ let mut rtp_buffer =
+ RTPBuffer::from_buffer_readable(&buffer).map_err(|_| gst::FlowError::Error)?;
+ (
+ rtp_buffer.get_seq(),
+ rtp_buffer.get_timestamp(),
+ rtp_buffer.get_payload_type(),
+ )
+ };
+
+ let mut pts = buffer.get_pts();
+ let mut dts = buffer.get_dts();
+ let mut estimated_dts = false;
+
+ gst_log!(
+ CAT,
+ obj: element,
+ "Storing buffer, seq: {}, rtptime: {}, pt: {}",
+ seq,
+ rtptime,
+ pt
+ );
+
+ if dts == gst::CLOCK_TIME_NONE {
+ dts = pts;
+ } else if pts == gst::CLOCK_TIME_NONE {
+ pts = dts;
+ }
+
+ if dts == gst::CLOCK_TIME_NONE {
+ dts = get_current_running_time(element);
+ pts = dts;
+
+ estimated_dts = state.clock_rate.is_some();
+ } else {
+ dts = state.segment.to_running_time(dts);
+ }
+
+ if state.clock_rate.is_none() {
+ inner.ips_rtptime = Some(rtptime);
+ inner.ips_pts = pts;
+ }
+
+ if inner.last_pt != Some(pt) {
+ inner.last_pt = Some(pt);
+ state.clock_rate = None;
+
+ gst_debug!(CAT, obj: pad, "New payload type: {}", pt);
+
+ if let Some(caps) = pad.get_current_caps() {
+ /* Ignore errors at this point, as we want to emit request-pt-map */
+ let _ = self.parse_caps(inner, &mut state, element, &caps, pt);
+ }
+ }
+
+ if state.clock_rate.is_none() {
+ let caps = element
+ .emit("request-pt-map", &[&(pt as u32)])
+ .map_err(|_| gst::FlowError::Error)?
+ .ok_or(gst::FlowError::Error)?
+ .get::<gst::Caps>()
+ .map_err(|_| gst::FlowError::Error)?
+ .ok_or(gst::FlowError::Error)?;
+ self.parse_caps(inner, &mut state, element, &caps, pt)?;
+ }
+
+ inner.packet_rate_ctx.update(seq, rtptime);
+
+ let max_dropout = inner
+ .packet_rate_ctx
+ .get_max_dropout(max_dropout_time as i32);
+ let max_misorder = inner
+ .packet_rate_ctx
+ .get_max_dropout(max_misorder_time as i32);
+
+ pts = state.jbuf.borrow().calculate_pts(
+ dts,
+ estimated_dts,
+ rtptime,
+ element.get_base_time(),
+ 0,
+ false,
+ );
+
+ if pts.is_none() {
+ gst_debug!(
+ CAT,
+ obj: element,
+ "cannot calculate a valid pts for #{}, discard",
+ seq
+ );
+ return Ok(gst::FlowSuccess::Ok);
+ }
+
+ if let Some(last_in_seqnum) = inner.last_in_seqnum {
+ let gap = gst_rtp::compare_seqnum(last_in_seqnum as u16, seq);
+ if gap == 1 {
+ self.calculate_packet_spacing(inner, &mut state, rtptime, pts);
+ } else {
+ if (gap != -1 && gap < -(max_misorder as i32)) || (gap >= max_dropout as i32) {
+ let reset = self.handle_big_gap_buffer(inner, element, buffer, pt);
+ if reset {
+ // Handle reset in `enqueue_item` to avoid recursion
+ return Err(gst::FlowError::CustomError);
+ } else {
+ return Ok(gst::FlowSuccess::Ok);
+ }
+ }
+ inner.ips_pts = gst::CLOCK_TIME_NONE;
+ inner.ips_rtptime = None;
+ }
+
+ inner.gap_packets.clear();
+ }
+
+ if let Some(last_popped_seqnum) = state.last_popped_seqnum {
+ let gap = gst_rtp::compare_seqnum(last_popped_seqnum, seq);
+
+ if gap <= 0 {
+ state.stats.num_late += 1;
+ gst_debug!(CAT, obj: element, "Dropping late {}", seq);
+ return Ok(gst::FlowSuccess::Ok);
+ }
+ }
+
+ inner.last_in_seqnum = Some(seq);
+
+ let jb_item = if estimated_dts {
+ RTPJitterBufferItem::new(buffer, gst::CLOCK_TIME_NONE, pts, Some(seq), rtptime)
+ } else {
+ RTPJitterBufferItem::new(buffer, dts, pts, Some(seq), rtptime)
+ };
+
+ let (success, _, _) = state.jbuf.borrow().insert(jb_item);
+
+ if !success {
+ /* duplicate */
+ return Ok(gst::FlowSuccess::Ok);
+ }
+
+ if Some(rtptime) == inner.last_rtptime {
+ state.equidistant -= 2;
+ } else {
+ state.equidistant += 1;
+ }
+
+ state.equidistant = min(max(state.equidistant, -7), 7);
+
+ inner.last_rtptime = Some(rtptime);
+
+ if state.earliest_pts.is_none()
+ || (pts.is_some()
+ && (pts < state.earliest_pts
+ || (pts == state.earliest_pts
+ && state
+ .earliest_seqnum
+ .map(|earliest_seqnum| seq > earliest_seqnum)
+ .unwrap_or(false))))
+ {
+ state.earliest_pts = pts;
+ state.earliest_seqnum = Some(seq);
+ }
+
+ gst_log!(CAT, obj: pad, "Stored buffer");
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+
+ fn enqueue_item(
+ &self,
+ pad: &gst::Pad,
+ element: &gst::Element,
+ buffer: Option<gst::Buffer>,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let mut inner = self.0.lock().unwrap();
+
+ let mut buffers = VecDeque::new();
+ if let Some(buf) = buffer {
+ buffers.push_back(buf);
+ }
+
+ // This is to avoid recursion with `store`, `reset` and `enqueue_item`
+ while let Some(buf) = buffers.pop_front() {
+ if let Err(err) = self.store(&mut inner, pad, element, buf) {
+ match err {
+ gst::FlowError::CustomError => {
+ let jb = JitterBuffer::from_instance(element);
+ let mut state = jb.state.lock().unwrap();
+ for gap_packet in self.reset(&mut inner, &mut state, element) {
+ buffers.push_back(gap_packet.buffer);
+ }
+ }
+ other => return Err(other),
+ }
+ }
+ }
+
+ let jb = JitterBuffer::from_instance(element);
+ let mut state = jb.state.lock().unwrap();
+
+ let (latency, context_wait) = {
+ let settings = jb.settings.lock().unwrap();
+ (
+ settings.latency_ms as u64 * gst::MSECOND,
+ settings.context_wait as u64 * gst::MSECOND,
+ )
+ };
+
+ // Reschedule if needed
+ let (_, next_wakeup) =
+ jb.src_pad_handler
+ .get_next_wakeup(&element, &state, latency, context_wait);
+ if let Some((next_wakeup, _)) = next_wakeup {
+ if let Some((previous_next_wakeup, ref abort_handle)) = state.wait_handle {
+ if previous_next_wakeup.is_none() || previous_next_wakeup > next_wakeup {
+ gst_debug!(
+ CAT,
+ obj: pad,
+ "Rescheduling for new item {} < {}",
+ next_wakeup,
+ previous_next_wakeup
+ );
+ abort_handle.abort();
+ state.wait_handle = None;
+ }
+ }
+ }
+ state.last_res
+ }
+}
+
+impl PadSinkHandler for SinkHandler {
+ type ElementImpl = JitterBuffer;
+
+ fn sink_chain(
+ &self,
+ pad: &PadSinkRef,
+ _jb: &JitterBuffer,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ let this = self.clone();
+
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+
+ gst_debug!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer);
+ this.enqueue_item(pad.gst_pad(), &element, Some(buffer))
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ pad: &PadSinkRef,
+ jb: &JitterBuffer,
+ _element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ if let EventView::FlushStart(..) = event.view() {
+ jb.task.cancel();
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ jb.src_pad.gst_pad().push_event(event)
+ }
+
+ fn sink_event_serialized(
+ &self,
+ pad: &PadSinkRef,
+ _jb: &JitterBuffer,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ use gst::EventView;
+
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let jb = JitterBuffer::from_instance(&element);
+
+ let mut forward = true;
+ match event.view() {
+ EventView::Segment(e) => {
+ let mut state = jb.state.lock().unwrap();
+ state.segment = e
+ .get_segment()
+ .clone()
+ .downcast::<gst::format::Time>()
+ .unwrap();
+ }
+ EventView::FlushStop(..) => {
+ jb.flush_stop(&element);
+ }
+ EventView::Eos(..) => {
+ let mut state = jb.state.lock().unwrap();
+ state.eos = true;
+ if let Some((_, abort_handle)) = state.wait_handle.take() {
+ abort_handle.abort();
+ }
+ forward = false;
+ }
+ _ => (),
+ };
+
+ if forward {
+ // FIXME: These events should really be queued up and stay in order
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding serialized {:?}", event);
+ jb.src_pad.push_event(event).await
+ } else {
+ true
+ }
+ }
+ .boxed()
+ }
+}
+
+#[derive(Clone)]
+struct SrcHandler;
+
+impl SrcHandler {
+ fn new() -> Self {
+ SrcHandler
+ }
+
+ fn clear(&self) {}
+
+ fn generate_lost_events(
+ &self,
+ state: &mut State,
+ element: &gst::Element,
+ seqnum: u16,
+ pts: gst::ClockTime,
+ discont: &mut bool,
+ ) -> Vec<gst::Event> {
+ let (latency_ns, do_lost) = {
+ let jb = JitterBuffer::from_instance(element);
+ let settings = jb.settings.lock().unwrap();
+ (
+ settings.latency_ms as u64 * gst::MSECOND.nseconds().unwrap(),
+ settings.do_lost,
+ )
+ };
+
+ let mut events = vec![];
+
+ let last_popped_seqnum = match state.last_popped_seqnum {
+ None => return events,
+ Some(seq) => seq,
+ };
+
+ gst_debug!(
+ CAT,
+ obj: element,
+ "Generating lost events seq: {}, last popped seq: {:?}",
+ seqnum,
+ last_popped_seqnum,
+ );
+
+ let mut lost_seqnum = last_popped_seqnum.wrapping_add(1);
+ let gap = gst_rtp::compare_seqnum(lost_seqnum, seqnum) as i64;
+
+ if gap > 0 {
+ let interval =
+ pts.nseconds().unwrap() as i64 - state.last_popped_pts.nseconds().unwrap() as i64;
+ let gap = gap as u64;
+ let spacing = if interval >= 0 {
+ interval as u64 / (gap + 1)
+ } else {
+ 0
+ };
+
+ *discont = true;
+
+ if state.equidistant > 0 && gap > 1 && gap * spacing > latency_ns {
+ let n_packets = gap - latency_ns / spacing;
+
+ if do_lost {
+ let s = gst::Structure::new(
+ "GstRTPPacketLost",
+ &[
+ ("seqnum", &(lost_seqnum as u32)),
+ (
+ "timestamp",
+ &(state.last_popped_pts + gst::ClockTime(Some(spacing))),
+ ),
+ ("duration", &(n_packets * spacing)),
+ ("retry", &0),
+ ],
+ );
+
+ events.push(gst::Event::new_custom_downstream(s).build());
+ }
+
+ lost_seqnum = lost_seqnum.wrapping_add(n_packets as u16);
+ state.last_popped_pts += gst::ClockTime(Some(n_packets * spacing));
+ state.stats.num_lost += n_packets;
+ }
+
+ while lost_seqnum != seqnum {
+ let timestamp = state.last_popped_pts + gst::ClockTime(Some(spacing));
+ let duration = if state.equidistant > 0 { spacing } else { 0 };
+
+ state.last_popped_pts = timestamp;
+
+ if do_lost {
+ let s = gst::Structure::new(
+ "GstRTPPacketLost",
+ &[
+ ("seqnum", &(lost_seqnum as u32)),
+ ("timestamp", &timestamp),
+ ("duration", &duration),
+ ("retry", &0),
+ ],
+ );
+
+ events.push(gst::Event::new_custom_downstream(s).build());
+ }
+
+ state.stats.num_lost += 1;
+
+ lost_seqnum = lost_seqnum.wrapping_add(1);
+ }
+ }
+
+ events
+ }
+
+ async fn pop_and_push(
+ &self,
+ element: &gst::Element,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let jb = JitterBuffer::from_instance(element);
+
+ let (lost_events, buffer, seq) = {
+ let mut state = jb.state.lock().unwrap();
+
+ let mut discont = false;
+ let (jb_item, _) = state.jbuf.borrow().pop();
+
+ let jb_item = match jb_item {
+ None => {
+ if state.eos {
+ return Err(gst::FlowError::Eos);
+ } else {
+ return Ok(gst::FlowSuccess::Ok);
+ }
+ }
+ Some(item) => item,
+ };
+
+ let dts = jb_item.get_dts();
+ let pts = jb_item.get_pts();
+ let seq = jb_item.get_seqnum();
+ let mut buffer = jb_item.into_buffer();
+
+ let lost_events = {
+ let buffer = buffer.make_mut();
+
+ buffer.set_dts(state.segment.to_running_time(dts));
+ buffer.set_pts(state.segment.to_running_time(pts));
+
+ if state.last_popped_pts.is_some() && buffer.get_pts() < state.last_popped_pts {
+ buffer.set_pts(state.last_popped_pts)
+ }
+
+ let lost_events = if let Some(seq) = seq {
+ self.generate_lost_events(&mut state, element, seq, pts, &mut discont)
+ } else {
+ vec![]
+ };
+
+ if state.discont {
+ discont = true;
+ state.discont = false;
+ }
+
+ if discont {
+ buffer.set_flags(gst::BufferFlags::DISCONT);
+ }
+
+ lost_events
+ };
+
+ state.last_popped_pts = buffer.get_pts();
+ if let Some(pts) = state.last_popped_pts.nseconds() {
+ state.position = pts.into();
+ }
+ state.last_popped_seqnum = seq;
+
+ state.stats.num_pushed += 1;
+
+ (lost_events, buffer, seq)
+ };
+
+ for event in lost_events {
+ gst_debug!(CAT, obj: jb.src_pad.gst_pad(), "Pushing lost event {:?}", event);
+ let _ = jb.src_pad.push_event(event).await;
+ }
+
+ gst_debug!(CAT, obj: jb.src_pad.gst_pad(), "Pushing {:?} with seq {:?}", buffer, seq);
+
+ jb.src_pad.push(buffer).await
+ }
+
+ fn get_next_wakeup(
+ &self,
+ element: &gst::Element,
+ state: &State,
+ latency: gst::ClockTime,
+ context_wait: gst::ClockTime,
+ ) -> (gst::ClockTime, Option<(gst::ClockTime, Duration)>) {
+ let now = get_current_running_time(element);
+
+ gst_debug!(
+ CAT,
+ obj: element,
+ "Now is {}, EOS {}, earliest pts is {}, packet_spacing {} and latency {}",
+ now,
+ state.eos,
+ state.earliest_pts,
+ state.packet_spacing,
+ latency
+ );
+
+ if state.eos {
+ gst_debug!(CAT, obj: element, "EOS, not waiting");
+ return (now, Some((now, Duration::from_nanos(0))));
+ }
+
+ if state.earliest_pts.is_none() {
+ return (now, None);
+ }
+
+ let next_wakeup = state.earliest_pts + latency - state.packet_spacing - context_wait / 2;
+
+ let delay = {
+ if next_wakeup > now {
+ (next_wakeup - now).nseconds().unwrap()
+ } else {
+ 0
+ }
+ };
+
+ gst_debug!(
+ CAT,
+ obj: element,
+ "Next wakeup at {} with delay {}",
+ next_wakeup,
+ delay
+ );
+
+ (now, Some((next_wakeup, Duration::from_nanos(delay))))
+ }
+}
+
+impl PadSrcHandler for SrcHandler {
+ type ElementImpl = JitterBuffer;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ jb: &JitterBuffer,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ match event.view() {
+ EventView::FlushStart(..) => {
+ jb.task.cancel();
+ }
+ EventView::FlushStop(..) => {
+ jb.flush_stop(element);
+ }
+ _ => (),
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ jb.sink_pad.gst_pad().push_event(event)
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ jb: &JitterBuffer,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", query);
+
+ match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ let mut peer_query = gst::query::Query::new_latency();
+
+ let ret = jb.sink_pad.gst_pad().peer_query(&mut peer_query);
+
+ if ret {
+ let settings = jb.settings.lock().unwrap();
+ let (_, mut min_latency, _) = peer_query.get_result();
+ min_latency += (settings.latency_ms as u64) * gst::SECOND;
+ let max_latency = gst::CLOCK_TIME_NONE;
+
+ q.set(true, min_latency, max_latency);
+ }
+
+ ret
+ }
+ QueryView::Position(ref mut q) => {
+ if q.get_format() != gst::Format::Time {
+ jb.sink_pad.gst_pad().peer_query(query)
+ } else {
+ let state = jb.state.lock().unwrap();
+ let position = state.position;
+ q.set(position);
+ true
+ }
+ }
+ _ => jb.sink_pad.gst_pad().peer_query(query),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Stats {
+ num_pushed: u64,
+ num_lost: u64,
+ num_late: u64,
+}
+
+impl Default for Stats {
+ fn default() -> Self {
+ Self {
+ num_pushed: 0,
+ num_lost: 0,
+ num_late: 0,
+ }
+ }
+}
+
+// Shared state between element, sink and source pad
+struct State {
+ jbuf: glib::SendUniqueCell<RTPJitterBuffer>,
+
+ last_res: Result<gst::FlowSuccess, gst::FlowError>,
+ position: gst::ClockTime,
+
+ segment: gst::FormattedSegment<gst::ClockTime>,
+ clock_rate: Option<u32>,
+
+ packet_spacing: gst::ClockTime,
+ equidistant: i32,
+
+ discont: bool,
+ eos: bool,
+
+ last_popped_seqnum: Option<u16>,
+ last_popped_pts: gst::ClockTime,
+
+ stats: Stats,
+
+ earliest_pts: gst::ClockTime,
+ earliest_seqnum: Option<u16>,
+
+ wait_handle: Option<(gst::ClockTime, AbortHandle)>,
+}
+
+impl Default for State {
+ fn default() -> State {
+ State {
+ jbuf: glib::SendUniqueCell::new(RTPJitterBuffer::new()).unwrap(),
+
+ last_res: Ok(gst::FlowSuccess::Ok),
+ position: gst::CLOCK_TIME_NONE,
+
+ segment: gst::FormattedSegment::<gst::ClockTime>::new(),
+ clock_rate: None,
+
+ packet_spacing: gst::ClockTime(Some(0)),
+ equidistant: 0,
+
+ discont: true,
+ eos: false,
+
+ last_popped_seqnum: None,
+ last_popped_pts: gst::CLOCK_TIME_NONE,
+
+ stats: Stats::default(),
+
+ earliest_pts: gst::CLOCK_TIME_NONE,
+ earliest_seqnum: None,
+
+ wait_handle: None,
+ }
+ }
+}
+
+struct JitterBuffer {
+ sink_pad: PadSink,
+ src_pad: PadSrc,
+ sink_pad_handler: SinkHandler,
+ src_pad_handler: SrcHandler,
+ task: Task,
+ state: StdMutex<State>,
+ settings: StdMutex<Settings>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-jitterbuffer",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing jitterbuffer"),
+ );
+}
+
+impl JitterBuffer {
+ fn clear_pt_map(&self, element: &gst::Element) {
+ gst_info!(CAT, obj: element, "Clearing PT map");
+
+ let mut state = self.state.lock().unwrap();
+ state.clock_rate = None;
+ state.jbuf.borrow().reset_skew();
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_info!(CAT, obj: element, "Preparing");
+
+ let context = {
+ let settings = self.settings.lock().unwrap();
+ Context::acquire(&settings.context, settings.context_wait).unwrap()
+ };
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+
+ self.src_pad.prepare(&self.src_pad_handler);
+ self.sink_pad.prepare(&self.sink_pad_handler);
+
+ gst_info!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ self.task.unprepare().unwrap();
+ self.sink_pad.unprepare();
+ self.src_pad.unprepare();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+ }
+
+ fn start(&self, element: &gst::Element) {
+ gst_debug!(CAT, obj: element, "Starting");
+
+ *self.state.lock().unwrap() = State::default();
+ self.sink_pad_handler.clear();
+ self.src_pad_handler.clear();
+
+ self.start_task(element);
+
+ gst_debug!(CAT, obj: element, "Started");
+ }
+
+ fn start_task(&self, element: &gst::Element) {
+ let src_pad_handler = self.src_pad_handler.clone();
+ let element = element.clone();
+
+ self.task.start(move || {
+ let src_pad_handler = src_pad_handler.clone();
+ let element = element.clone();
+
+ async move {
+ let jb = JitterBuffer::from_instance(&element);
+ let (latency, context_wait) = {
+ let settings = jb.settings.lock().unwrap();
+ (
+ settings.latency_ms as u64 * gst::MSECOND,
+ settings.context_wait as u64 * gst::MSECOND,
+ )
+ };
+
+ loop {
+ let delay_fut = {
+ let mut state = jb.state.lock().unwrap();
+ let (_, next_wakeup) = src_pad_handler.get_next_wakeup(
+ &element,
+ &state,
+ latency,
+ context_wait,
+ );
+
+ let (delay_fut, abort_handle) = match next_wakeup {
+ Some((_, delay)) if delay == Duration::from_nanos(0) => (None, None),
+ _ => {
+ let (delay_fut, abort_handle) = abortable(async move {
+ match next_wakeup {
+ Some((_, delay)) => {
+ runtime::time::delay_for(delay).await;
+ }
+ None => {
+ future::pending::<()>().await;
+ }
+ };
+ });
+
+ let next_wakeup =
+ next_wakeup.map(|w| w.0).unwrap_or(gst::CLOCK_TIME_NONE);
+ (Some(delay_fut), Some((next_wakeup, abort_handle)))
+ }
+ };
+
+ state.wait_handle = abort_handle;
+
+ delay_fut
+ };
+
+ // Got aborted, reschedule if needed
+ if let Some(delay_fut) = delay_fut {
+ gst_debug!(CAT, obj: &element, "Waiting");
+ if let Err(Aborted) = delay_fut.await {
+ gst_debug!(CAT, obj: &element, "Waiting aborted");
+ return glib::Continue(true);
+ }
+ }
+
+ let (head_pts, head_seq) = {
+ let state = jb.state.lock().unwrap();
+ //
+ // Check earliest PTS as we have just taken the lock
+ let (now, next_wakeup) = src_pad_handler.get_next_wakeup(
+ &element,
+ &state,
+ latency,
+ context_wait,
+ );
+
+ gst_debug!(
+ CAT,
+ obj: &element,
+ "Woke up at {}, earliest_pts {}",
+ now,
+ state.earliest_pts
+ );
+
+ if let Some((next_wakeup, _)) = next_wakeup {
+ if next_wakeup > now {
+ // Reschedule and wait a bit longer in the next iteration
+ return glib::Continue(true);
+ }
+ } else {
+ return glib::Continue(true);
+ }
+
+ let (head_pts, head_seq) = state.jbuf.borrow().peek();
+
+ (head_pts, head_seq)
+ };
+
+ let res = src_pad_handler.pop_and_push(&element).await;
+
+ {
+ let mut state = jb.state.lock().unwrap();
+
+ state.last_res = res;
+
+ if head_pts == state.earliest_pts && head_seq == state.earliest_seqnum {
+ let (earliest_pts, earliest_seqnum) =
+ state.jbuf.borrow().find_earliest();
+ state.earliest_pts = earliest_pts;
+ state.earliest_seqnum = earliest_seqnum;
+ }
+
+ if res.is_ok() {
+ // Return and reschedule if the next packet would be in the future
+ // Check earliest PTS as we have just taken the lock
+ let (now, next_wakeup) = src_pad_handler.get_next_wakeup(
+ &element,
+ &state,
+ latency,
+ context_wait,
+ );
+ if let Some((next_wakeup, _)) = next_wakeup {
+ if next_wakeup > now {
+ // Reschedule and wait a bit longer in the next iteration
+ return glib::Continue(true);
+ }
+ } else {
+ return glib::Continue(true);
+ }
+ }
+ }
+
+ match res {
+ Ok(_) => (),
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(CAT, obj: &element, "Pushing EOS event",);
+ let event = gst::Event::new_eos().build();
+ let _ = jb.src_pad.push_event(event).await;
+ return glib::Continue(false);
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(CAT, obj: &element, "Flushing",);
+ return glib::Continue(false);
+ }
+ Err(err) => {
+ gst_error!(CAT, obj: &element, "Error {}", err,);
+ return glib::Continue(false);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ fn stop(&self, element: &gst::Element) {
+ gst_debug!(CAT, obj: element, "Stopping");
+
+ if let Some((_, abort_handle)) = self.state.lock().unwrap().wait_handle.take() {
+ abort_handle.abort();
+ }
+
+ self.task.stop();
+
+ self.src_pad_handler.clear();
+ self.sink_pad_handler.clear();
+ *self.state.lock().unwrap() = State::default();
+
+ gst_debug!(CAT, obj: element, "Stopped");
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ self.task.stop();
+ self.start(element);
+ }
+}
+
+impl ObjectSubclass for JitterBuffer {
+ const NAME: &'static str = "RsTsJitterBuffer";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing jitterbuffer",
+ "Generic",
+ "Simple jitterbuffer",
+ "Mathieu Duponchelle <mathieu@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+ klass.add_signal(
+ "request-pt-map",
+ glib::SignalFlags::RUN_LAST,
+ &[u32::static_type()],
+ gst::Caps::static_type(),
+ );
+
+ klass.add_signal_with_class_handler(
+ "clear-pt-map",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[],
+ glib::types::Type::Unit,
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let jb = Self::from_instance(&element);
+ jb.clear_pt_map(&element);
+ None
+ },
+ );
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+ klass.install_properties(&PROPERTIES);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ sink_pad: PadSink::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("sink").unwrap(),
+ Some("sink"),
+ )),
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ sink_pad_handler: SinkHandler::new(),
+ src_pad_handler: SrcHandler::new(),
+ task: Task::default(),
+ state: StdMutex::new(State::default()),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for JitterBuffer {
+ glib_object_impl!();
+
+ fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("latency", ..) => {
+ let latency_ms = {
+ let mut settings = self.settings.lock().unwrap();
+ settings.latency_ms = value.get_some().expect("type checked upstream");
+ settings.latency_ms as u64
+ };
+
+ let state = self.state.lock().unwrap();
+ state.jbuf.borrow().set_delay(latency_ms * gst::MSECOND);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ let _ =
+ element.post_message(&gst::Message::new_latency().src(Some(element)).build());
+ }
+ subclass::Property("do-lost", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.do_lost = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-dropout-time", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.max_dropout_time = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-misorder-time", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.max_misorder_time = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("context", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ let mut settings = self.settings.lock().unwrap();
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ match *prop {
+ subclass::Property("latency", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.latency_ms.to_value())
+ }
+ subclass::Property("do-lost", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.do_lost.to_value())
+ }
+ subclass::Property("max-dropout-time", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.max_dropout_time.to_value())
+ }
+ subclass::Property("max-misorder-time", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.max_misorder_time.to_value())
+ }
+ subclass::Property("stats", ..) => {
+ let state = self.state.lock().unwrap();
+ let s = gst::Structure::new(
+ "application/x-rtp-jitterbuffer-stats",
+ &[
+ ("num-pushed", &state.stats.num_pushed),
+ ("num-lost", &state.stats.num_lost),
+ ("num-late", &state.stats.num_late),
+ ],
+ );
+ Ok(s.to_value())
+ }
+ subclass::Property("context", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.context.to_value())
+ }
+ subclass::Property("context-wait", ..) => {
+ let settings = self.settings.lock().unwrap();
+ Ok(settings.context_wait.to_value())
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.sink_pad.gst_pad()).unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+ }
+}
+
+impl ElementImpl for JitterBuffer {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element);
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element);
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ self.start(element);
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-jitterbuffer",
+ gst::Rank::None,
+ JitterBuffer::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/mod.rs b/generic/gst-plugin-threadshare/src/jitterbuffer/mod.rs
new file mode 100644
index 000000000..81873205d
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/mod.rs
@@ -0,0 +1,458 @@
+// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib_sys as glib_ffi;
+use gstreamer_sys as gst_ffi;
+
+use std::u32;
+
+#[allow(clippy::module_inception)]
+pub mod jitterbuffer;
+
+pub mod ffi {
+ use glib_ffi::{gboolean, gpointer, GList, GType};
+ use glib_sys as glib_ffi;
+
+ use gst_ffi::GstClockTime;
+ use gstreamer_sys as gst_ffi;
+ use libc::{c_int, c_uint, c_ulonglong, c_ushort, c_void};
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct RTPJitterBufferItem {
+ pub data: gpointer,
+ pub next: *mut GList,
+ pub prev: *mut GList,
+ pub r#type: c_uint,
+ pub dts: GstClockTime,
+ pub pts: GstClockTime,
+ pub seqnum: c_uint,
+ pub count: c_uint,
+ pub rtptime: c_uint,
+ }
+
+ #[repr(C)]
+ pub struct RTPJitterBuffer(c_void);
+
+ #[repr(C)]
+ #[derive(Copy, Clone)]
+ pub struct RTPPacketRateCtx {
+ probed: gboolean,
+ clock_rate: c_int,
+ last_seqnum: c_ushort,
+ last_ts: c_ulonglong,
+ avg_packet_rate: c_uint,
+ }
+
+ pub type RTPJitterBufferMode = c_int;
+ pub const RTP_JITTER_BUFFER_MODE_NONE: RTPJitterBufferMode = 0;
+ pub const RTP_JITTER_BUFFER_MODE_SLAVE: RTPJitterBufferMode = 1;
+ pub const RTP_JITTER_BUFFER_MODE_BUFFER: RTPJitterBufferMode = 2;
+ pub const RTP_JITTER_BUFFER_MODE_SYNCED: RTPJitterBufferMode = 4;
+
+ extern "C" {
+ pub fn rtp_jitter_buffer_new() -> *mut RTPJitterBuffer;
+ pub fn rtp_jitter_buffer_get_type() -> GType;
+ #[allow(dead_code)]
+ pub fn rtp_jitter_buffer_get_mode(jbuf: *mut RTPJitterBuffer) -> RTPJitterBufferMode;
+ #[allow(dead_code)]
+ pub fn rtp_jitter_buffer_set_mode(jbuf: *mut RTPJitterBuffer, mode: RTPJitterBufferMode);
+ #[allow(dead_code)]
+ pub fn rtp_jitter_buffer_get_delay(jbuf: *mut RTPJitterBuffer) -> GstClockTime;
+ pub fn rtp_jitter_buffer_set_delay(jbuf: *mut RTPJitterBuffer, delay: GstClockTime);
+ pub fn rtp_jitter_buffer_set_clock_rate(jbuf: *mut RTPJitterBuffer, clock_rate: c_uint);
+ #[allow(dead_code)]
+ pub fn rtp_jitter_buffer_get_clock_rate(jbuf: *mut RTPJitterBuffer) -> c_uint;
+ pub fn rtp_jitter_buffer_reset_skew(jbuf: *mut RTPJitterBuffer);
+
+ pub fn rtp_jitter_buffer_flush(jbuf: *mut RTPJitterBuffer, free_func: glib_ffi::GFunc);
+ pub fn rtp_jitter_buffer_find_earliest(
+ jbuf: *mut RTPJitterBuffer,
+ pts: *mut GstClockTime,
+ seqnum: *mut c_uint,
+ );
+ pub fn rtp_jitter_buffer_calculate_pts(
+ jbuf: *mut RTPJitterBuffer,
+ dts: GstClockTime,
+ estimated_dts: gboolean,
+ rtptime: c_uint,
+ base_time: GstClockTime,
+ gap: c_int,
+ is_rtx: gboolean,
+ ) -> GstClockTime;
+ pub fn rtp_jitter_buffer_insert(
+ jbuf: *mut RTPJitterBuffer,
+ item: *mut RTPJitterBufferItem,
+ head: *mut gboolean,
+ percent: *mut c_int,
+ ) -> gboolean;
+ pub fn rtp_jitter_buffer_pop(
+ jbuf: *mut RTPJitterBuffer,
+ percent: *mut c_int,
+ ) -> *mut RTPJitterBufferItem;
+ pub fn rtp_jitter_buffer_peek(jbuf: *mut RTPJitterBuffer) -> *mut RTPJitterBufferItem;
+
+ pub fn gst_rtp_packet_rate_ctx_reset(ctx: *mut RTPPacketRateCtx, clock_rate: c_int);
+ pub fn gst_rtp_packet_rate_ctx_update(
+ ctx: *mut RTPPacketRateCtx,
+ seqnum: c_ushort,
+ ts: c_uint,
+ ) -> c_uint;
+ pub fn gst_rtp_packet_rate_ctx_get_max_dropout(
+ ctx: *mut RTPPacketRateCtx,
+ time_ms: c_int,
+ ) -> c_uint;
+ #[allow(dead_code)]
+ pub fn gst_rtp_packet_rate_ctx_get_max_disorder(
+ ctx: *mut RTPPacketRateCtx,
+ time_ms: c_int,
+ ) -> c_uint;
+ }
+}
+
+use glib::prelude::*;
+use glib::translate::*;
+use glib::{glib_object_wrapper, glib_wrapper};
+
+use std::mem;
+use std::ptr;
+
+glib_wrapper! {
+ pub struct RTPJitterBuffer(Object<ffi::RTPJitterBuffer, RTPJitterBufferClass>);
+
+ match fn {
+ get_type => || ffi::rtp_jitter_buffer_get_type(),
+ }
+}
+
+unsafe impl glib::SendUnique for RTPJitterBuffer {
+ fn is_unique(&self) -> bool {
+ self.ref_count() == 1
+ }
+}
+
+impl ToGlib for RTPJitterBufferMode {
+ type GlibType = ffi::RTPJitterBufferMode;
+
+ fn to_glib(&self) -> ffi::RTPJitterBufferMode {
+ match *self {
+ RTPJitterBufferMode::None => ffi::RTP_JITTER_BUFFER_MODE_NONE,
+ RTPJitterBufferMode::Slave => ffi::RTP_JITTER_BUFFER_MODE_SLAVE,
+ RTPJitterBufferMode::Buffer => ffi::RTP_JITTER_BUFFER_MODE_BUFFER,
+ RTPJitterBufferMode::Synced => ffi::RTP_JITTER_BUFFER_MODE_SYNCED,
+ RTPJitterBufferMode::__Unknown(value) => value,
+ }
+ }
+}
+
+impl FromGlib<ffi::RTPJitterBufferMode> for RTPJitterBufferMode {
+ fn from_glib(value: ffi::RTPJitterBufferMode) -> Self {
+ match value {
+ 0 => RTPJitterBufferMode::None,
+ 1 => RTPJitterBufferMode::Slave,
+ 2 => RTPJitterBufferMode::Buffer,
+ 4 => RTPJitterBufferMode::Synced,
+ value => RTPJitterBufferMode::__Unknown(value),
+ }
+ }
+}
+
+pub struct RTPJitterBufferItem(Option<Box<ffi::RTPJitterBufferItem>>);
+
+unsafe impl Send for RTPJitterBufferItem {}
+
+impl RTPJitterBufferItem {
+ pub fn new(
+ buffer: gst::Buffer,
+ dts: gst::ClockTime,
+ pts: gst::ClockTime,
+ seqnum: Option<u16>,
+ rtptime: u32,
+ ) -> RTPJitterBufferItem {
+ unsafe {
+ RTPJitterBufferItem(Some(Box::new(ffi::RTPJitterBufferItem {
+ data: buffer.into_ptr() as *mut _,
+ next: ptr::null_mut(),
+ prev: ptr::null_mut(),
+ r#type: 0,
+ dts: dts.to_glib(),
+ pts: pts.to_glib(),
+ seqnum: seqnum.map(|s| s as u32).unwrap_or(u32::MAX),
+ count: 1,
+ rtptime,
+ })))
+ }
+ }
+
+ pub fn into_buffer(mut self) -> gst::Buffer {
+ unsafe {
+ let item = self.0.take().expect("Invalid wrapper");
+ let buf = item.data as *mut gst_ffi::GstBuffer;
+ from_glib_full(buf)
+ }
+ }
+
+ pub fn get_dts(&self) -> gst::ClockTime {
+ let item = self.0.as_ref().expect("Invalid wrapper");
+ if item.dts == gst_ffi::GST_CLOCK_TIME_NONE {
+ gst::CLOCK_TIME_NONE
+ } else {
+ gst::ClockTime(Some(item.dts))
+ }
+ }
+
+ pub fn get_pts(&self) -> gst::ClockTime {
+ let item = self.0.as_ref().expect("Invalid wrapper");
+ if item.pts == gst_ffi::GST_CLOCK_TIME_NONE {
+ gst::CLOCK_TIME_NONE
+ } else {
+ gst::ClockTime(Some(item.pts))
+ }
+ }
+
+ pub fn get_seqnum(&self) -> Option<u16> {
+ let item = self.0.as_ref().expect("Invalid wrapper");
+ if item.seqnum == u32::MAX {
+ None
+ } else {
+ Some(item.seqnum as u16)
+ }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_rtptime(&self) -> u32 {
+ let item = self.0.as_ref().expect("Invalid wrapper");
+ item.rtptime
+ }
+}
+
+impl Drop for RTPJitterBufferItem {
+ fn drop(&mut self) {
+ unsafe {
+ if let Some(ref item) = self.0 {
+ gst_ffi::gst_mini_object_unref(item.data as *mut _)
+ }
+ }
+ }
+}
+
+pub struct RTPPacketRateCtx(Box<ffi::RTPPacketRateCtx>);
+
+unsafe impl Send for RTPPacketRateCtx {}
+
+impl RTPPacketRateCtx {
+ pub fn new() -> RTPPacketRateCtx {
+ unsafe {
+ let mut ptr = std::mem::MaybeUninit::uninit();
+ ffi::gst_rtp_packet_rate_ctx_reset(ptr.as_mut_ptr(), -1);
+ RTPPacketRateCtx(Box::new(ptr.assume_init()))
+ }
+ }
+
+ pub fn reset(&mut self, clock_rate: i32) {
+ unsafe { ffi::gst_rtp_packet_rate_ctx_reset(&mut *self.0, clock_rate) }
+ }
+
+ pub fn update(&mut self, seqnum: u16, ts: u32) -> u32 {
+ unsafe { ffi::gst_rtp_packet_rate_ctx_update(&mut *self.0, seqnum, ts) }
+ }
+
+ pub fn get_max_dropout(&mut self, time_ms: i32) -> u32 {
+ unsafe { ffi::gst_rtp_packet_rate_ctx_get_max_dropout(&mut *self.0, time_ms) }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_max_disorder(&mut self, time_ms: i32) -> u32 {
+ unsafe { ffi::gst_rtp_packet_rate_ctx_get_max_disorder(&mut *self.0, time_ms) }
+ }
+}
+
+impl Default for RTPPacketRateCtx {
+ fn default() -> Self {
+ RTPPacketRateCtx::new()
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
+pub enum RTPJitterBufferMode {
+ r#None,
+ Slave,
+ Buffer,
+ Synced,
+ __Unknown(i32),
+}
+
+impl RTPJitterBuffer {
+ pub fn new() -> RTPJitterBuffer {
+ unsafe { from_glib_full(ffi::rtp_jitter_buffer_new()) }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_mode(&self) -> RTPJitterBufferMode {
+ unsafe { from_glib(ffi::rtp_jitter_buffer_get_mode(self.to_glib_none().0)) }
+ }
+
+ #[allow(dead_code)]
+ pub fn set_mode(&self, mode: RTPJitterBufferMode) {
+ unsafe { ffi::rtp_jitter_buffer_set_mode(self.to_glib_none().0, mode.to_glib()) }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_delay(&self) -> gst::ClockTime {
+ unsafe { from_glib(ffi::rtp_jitter_buffer_get_delay(self.to_glib_none().0)) }
+ }
+
+ pub fn set_delay(&self, delay: gst::ClockTime) {
+ unsafe { ffi::rtp_jitter_buffer_set_delay(self.to_glib_none().0, delay.to_glib()) }
+ }
+
+ pub fn set_clock_rate(&self, clock_rate: u32) {
+ unsafe { ffi::rtp_jitter_buffer_set_clock_rate(self.to_glib_none().0, clock_rate) }
+ }
+
+ #[allow(dead_code)]
+ pub fn get_clock_rate(&self) -> u32 {
+ unsafe { ffi::rtp_jitter_buffer_get_clock_rate(self.to_glib_none().0) }
+ }
+
+ pub fn calculate_pts(
+ &self,
+ dts: gst::ClockTime,
+ estimated_dts: bool,
+ rtptime: u32,
+ base_time: gst::ClockTime,
+ gap: i32,
+ is_rtx: bool,
+ ) -> gst::ClockTime {
+ unsafe {
+ let pts = ffi::rtp_jitter_buffer_calculate_pts(
+ self.to_glib_none().0,
+ dts.to_glib(),
+ estimated_dts.to_glib(),
+ rtptime,
+ base_time.to_glib(),
+ gap,
+ is_rtx.to_glib(),
+ );
+
+ if pts == gst_ffi::GST_CLOCK_TIME_NONE {
+ gst::CLOCK_TIME_NONE
+ } else {
+ pts.into()
+ }
+ }
+ }
+
+ pub fn insert(&self, mut item: RTPJitterBufferItem) -> (bool, bool, i32) {
+ unsafe {
+ let mut head = mem::MaybeUninit::uninit();
+ let mut percent = mem::MaybeUninit::uninit();
+ let box_ = item.0.take().expect("Invalid wrapper");
+ let ptr = Box::into_raw(box_);
+ let ret: bool = from_glib(ffi::rtp_jitter_buffer_insert(
+ self.to_glib_none().0,
+ ptr,
+ head.as_mut_ptr(),
+ percent.as_mut_ptr(),
+ ));
+ if !ret {
+ item.0 = Some(Box::from_raw(ptr));
+ }
+ (ret, from_glib(head.assume_init()), percent.assume_init())
+ }
+ }
+
+ pub fn find_earliest(&self) -> (gst::ClockTime, Option<u16>) {
+ unsafe {
+ let mut pts = mem::MaybeUninit::uninit();
+ let mut seqnum = mem::MaybeUninit::uninit();
+
+ ffi::rtp_jitter_buffer_find_earliest(
+ self.to_glib_none().0,
+ pts.as_mut_ptr(),
+ seqnum.as_mut_ptr(),
+ );
+ let pts = pts.assume_init();
+ let seqnum = seqnum.assume_init();
+
+ let seqnum = if seqnum == u32::MAX {
+ None
+ } else {
+ Some(seqnum as u16)
+ };
+
+ if pts == gst_ffi::GST_CLOCK_TIME_NONE {
+ (gst::CLOCK_TIME_NONE, seqnum)
+ } else {
+ (pts.into(), seqnum)
+ }
+ }
+ }
+
+ pub fn pop(&self) -> (Option<RTPJitterBufferItem>, i32) {
+ unsafe {
+ let mut percent = mem::MaybeUninit::uninit();
+ let item = ffi::rtp_jitter_buffer_pop(self.to_glib_none().0, percent.as_mut_ptr());
+
+ (
+ if item.is_null() {
+ None
+ } else {
+ Some(RTPJitterBufferItem(Some(Box::from_raw(item))))
+ },
+ percent.assume_init(),
+ )
+ }
+ }
+
+ pub fn peek(&self) -> (gst::ClockTime, Option<u16>) {
+ unsafe {
+ let item = ffi::rtp_jitter_buffer_peek(self.to_glib_none().0);
+ if item.is_null() {
+ (gst::CLOCK_TIME_NONE, None)
+ } else {
+ let seqnum = (*item).seqnum;
+ let seqnum = if seqnum == u32::MAX {
+ None
+ } else {
+ Some(seqnum as u16)
+ };
+ ((*item).pts.into(), seqnum)
+ }
+ }
+ }
+
+ pub fn flush(&self) {
+ unsafe extern "C" fn free_item(item: glib_ffi::gpointer, _: glib_ffi::gpointer) {
+ let _ = RTPJitterBufferItem(Some(Box::from_raw(item as *mut _)));
+ }
+
+ unsafe {
+ ffi::rtp_jitter_buffer_flush(self.to_glib_none().0, Some(free_item));
+ }
+ }
+
+ pub fn reset_skew(&self) {
+ unsafe { ffi::rtp_jitter_buffer_reset_skew(self.to_glib_none().0) }
+ }
+}
+
+impl Default for RTPJitterBuffer {
+ fn default() -> Self {
+ RTPJitterBuffer::new()
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.c b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.c
new file mode 100644
index 000000000..eee284065
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.c
@@ -0,0 +1,1409 @@
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <string.h>
+#include <stdlib.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+#include <gst/rtp/gstrtcpbuffer.h>
+
+#include "rtpjitterbuffer.h"
+
+GST_DEBUG_CATEGORY_STATIC (rtp_jitter_buffer_debug);
+#define GST_CAT_DEFAULT rtp_jitter_buffer_debug
+
+#define MAX_WINDOW RTP_JITTER_BUFFER_MAX_WINDOW
+#define MAX_TIME (2 * GST_SECOND)
+
+/* signals and args */
+enum
+{
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0
+};
+
+/* GObject vmethods */
+static void rtp_jitter_buffer_finalize (GObject * object);
+
+GType
+rtp_jitter_buffer_mode_get_type (void)
+{
+ static GType jitter_buffer_mode_type = 0;
+ static const GEnumValue jitter_buffer_modes[] = {
+ {RTP_JITTER_BUFFER_MODE_NONE, "Only use RTP timestamps", "none"},
+ {RTP_JITTER_BUFFER_MODE_SLAVE, "Slave receiver to sender clock", "slave"},
+ {RTP_JITTER_BUFFER_MODE_BUFFER, "Do low/high watermark buffering",
+ "buffer"},
+ {RTP_JITTER_BUFFER_MODE_SYNCED, "Synchronized sender and receiver clocks",
+ "synced"},
+ {0, NULL, NULL},
+ };
+
+ if (!jitter_buffer_mode_type) {
+ jitter_buffer_mode_type =
+ g_enum_register_static ("TsRTPJitterBufferMode", jitter_buffer_modes);
+ }
+ return jitter_buffer_mode_type;
+}
+
+/* static guint rtp_jitter_buffer_signals[LAST_SIGNAL] = { 0 }; */
+
+G_DEFINE_TYPE (RTPJitterBuffer, rtp_jitter_buffer, G_TYPE_OBJECT);
+
+static void
+rtp_jitter_buffer_class_init (RTPJitterBufferClass * klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = (GObjectClass *) klass;
+
+ gobject_class->finalize = rtp_jitter_buffer_finalize;
+
+ GST_DEBUG_CATEGORY_INIT (rtp_jitter_buffer_debug, "rtpjitterbuffer", 0,
+ "RTP Jitter Buffer");
+}
+
+static void
+rtp_jitter_buffer_init (RTPJitterBuffer * jbuf)
+{
+ g_mutex_init (&jbuf->clock_lock);
+
+ jbuf->packets = g_queue_new ();
+ jbuf->mode = RTP_JITTER_BUFFER_MODE_SLAVE;
+
+ rtp_jitter_buffer_reset_skew (jbuf);
+}
+
+static void
+rtp_jitter_buffer_finalize (GObject * object)
+{
+ RTPJitterBuffer *jbuf;
+
+ jbuf = RTP_JITTER_BUFFER_CAST (object);
+
+ if (jbuf->media_clock_synced_id)
+ g_signal_handler_disconnect (jbuf->media_clock,
+ jbuf->media_clock_synced_id);
+ if (jbuf->media_clock) {
+ /* Make sure to clear any clock master before releasing the clock */
+ gst_clock_set_master (jbuf->media_clock, NULL);
+ gst_object_unref (jbuf->media_clock);
+ }
+
+ if (jbuf->pipeline_clock)
+ gst_object_unref (jbuf->pipeline_clock);
+
+ g_queue_free (jbuf->packets);
+
+ g_mutex_clear (&jbuf->clock_lock);
+
+ G_OBJECT_CLASS (rtp_jitter_buffer_parent_class)->finalize (object);
+}
+
+/**
+ * rtp_jitter_buffer_new:
+ *
+ * Create an #RTPJitterBuffer.
+ *
+ * Returns: a new #RTPJitterBuffer. Use g_object_unref() after usage.
+ */
+RTPJitterBuffer *
+rtp_jitter_buffer_new (void)
+{
+ RTPJitterBuffer *jbuf;
+
+ jbuf = g_object_new (RTP_TYPE_JITTER_BUFFER, NULL);
+
+ return jbuf;
+}
+
+/**
+ * rtp_jitter_buffer_get_mode:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the current jitterbuffer mode.
+ *
+ * Returns: the current jitterbuffer mode.
+ */
+RTPJitterBufferMode
+rtp_jitter_buffer_get_mode (RTPJitterBuffer * jbuf)
+{
+ return jbuf->mode;
+}
+
+/**
+ * rtp_jitter_buffer_set_mode:
+ * @jbuf: an #RTPJitterBuffer
+ * @mode: a #RTPJitterBufferMode
+ *
+ * Set the buffering and clock slaving algorithm used in the @jbuf.
+ */
+void
+rtp_jitter_buffer_set_mode (RTPJitterBuffer * jbuf, RTPJitterBufferMode mode)
+{
+ jbuf->mode = mode;
+}
+
+GstClockTime
+rtp_jitter_buffer_get_delay (RTPJitterBuffer * jbuf)
+{
+ g_print ("%p getting delay (%" G_GUINT64_FORMAT ")\n", jbuf, jbuf->delay);
+ return jbuf->delay;
+}
+
+void
+rtp_jitter_buffer_set_delay (RTPJitterBuffer * jbuf, GstClockTime delay)
+{
+ jbuf->delay = delay;
+ jbuf->low_level = (delay * 15) / 100;
+ /* the high level is at 90% in order to release packets before we fill up the
+ * buffer up to the latency */
+ jbuf->high_level = (delay * 90) / 100;
+
+ GST_DEBUG ("delay %" GST_TIME_FORMAT ", min %" GST_TIME_FORMAT ", max %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (jbuf->delay),
+ GST_TIME_ARGS (jbuf->low_level), GST_TIME_ARGS (jbuf->high_level));
+}
+
+/**
+ * rtp_jitter_buffer_set_clock_rate:
+ * @jbuf: an #RTPJitterBuffer
+ * @clock_rate: the new clock rate
+ *
+ * Set the clock rate in the jitterbuffer.
+ */
+void
+rtp_jitter_buffer_set_clock_rate (RTPJitterBuffer * jbuf, guint32 clock_rate)
+{
+ if (jbuf->clock_rate != clock_rate) {
+ GST_DEBUG ("Clock rate changed from %" G_GUINT32_FORMAT " to %"
+ G_GUINT32_FORMAT, jbuf->clock_rate, clock_rate);
+ jbuf->clock_rate = clock_rate;
+ rtp_jitter_buffer_reset_skew (jbuf);
+ }
+}
+
+/**
+ * rtp_jitter_buffer_get_clock_rate:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the currently configure clock rate in @jbuf.
+ *
+ * Returns: the current clock-rate
+ */
+guint32
+rtp_jitter_buffer_get_clock_rate (RTPJitterBuffer * jbuf)
+{
+ return jbuf->clock_rate;
+}
+
+static void
+media_clock_synced_cb (GstClock * clock G_GNUC_UNUSED,
+ gboolean synced G_GNUC_UNUSED, RTPJitterBuffer * jbuf)
+{
+ GstClockTime internal, external;
+
+ g_mutex_lock (&jbuf->clock_lock);
+ if (jbuf->pipeline_clock) {
+ internal = gst_clock_get_internal_time (jbuf->media_clock);
+ external = gst_clock_get_time (jbuf->pipeline_clock);
+
+ gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1);
+ }
+ g_mutex_unlock (&jbuf->clock_lock);
+}
+
+/**
+ * rtp_jitter_buffer_set_media_clock:
+ * @jbuf: an #RTPJitterBuffer
+ * @clock: (transfer full): media #GstClock
+ * @clock_offset: RTP time at clock epoch or -1
+ *
+ * Sets the media clock for the media and the clock offset
+ *
+ */
+void
+rtp_jitter_buffer_set_media_clock (RTPJitterBuffer * jbuf, GstClock * clock,
+ guint64 clock_offset)
+{
+ g_mutex_lock (&jbuf->clock_lock);
+ if (jbuf->media_clock) {
+ if (jbuf->media_clock_synced_id)
+ g_signal_handler_disconnect (jbuf->media_clock,
+ jbuf->media_clock_synced_id);
+ jbuf->media_clock_synced_id = 0;
+ gst_object_unref (jbuf->media_clock);
+ }
+ jbuf->media_clock = clock;
+ jbuf->media_clock_offset = clock_offset;
+
+ if (jbuf->pipeline_clock && jbuf->media_clock &&
+ jbuf->pipeline_clock != jbuf->media_clock) {
+ jbuf->media_clock_synced_id =
+ g_signal_connect (jbuf->media_clock, "synced",
+ G_CALLBACK (media_clock_synced_cb), jbuf);
+ if (gst_clock_is_synced (jbuf->media_clock)) {
+ GstClockTime internal, external;
+
+ internal = gst_clock_get_internal_time (jbuf->media_clock);
+ external = gst_clock_get_time (jbuf->pipeline_clock);
+
+ gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1);
+ }
+
+ gst_clock_set_master (jbuf->media_clock, jbuf->pipeline_clock);
+ }
+ g_mutex_unlock (&jbuf->clock_lock);
+}
+
+/**
+ * rtp_jitter_buffer_set_pipeline_clock:
+ * @jbuf: an #RTPJitterBuffer
+ * @clock: pipeline #GstClock
+ *
+ * Sets the pipeline clock
+ *
+ */
+void
+rtp_jitter_buffer_set_pipeline_clock (RTPJitterBuffer * jbuf, GstClock * clock)
+{
+ g_mutex_lock (&jbuf->clock_lock);
+ if (jbuf->pipeline_clock)
+ gst_object_unref (jbuf->pipeline_clock);
+ jbuf->pipeline_clock = clock ? gst_object_ref (clock) : NULL;
+
+ if (jbuf->pipeline_clock && jbuf->media_clock &&
+ jbuf->pipeline_clock != jbuf->media_clock) {
+ if (gst_clock_is_synced (jbuf->media_clock)) {
+ GstClockTime internal, external;
+
+ internal = gst_clock_get_internal_time (jbuf->media_clock);
+ external = gst_clock_get_time (jbuf->pipeline_clock);
+
+ gst_clock_set_calibration (jbuf->media_clock, internal, external, 1, 1);
+ }
+
+ gst_clock_set_master (jbuf->media_clock, jbuf->pipeline_clock);
+ }
+ g_mutex_unlock (&jbuf->clock_lock);
+}
+
+gboolean
+rtp_jitter_buffer_get_rfc7273_sync (RTPJitterBuffer * jbuf)
+{
+ return jbuf->rfc7273_sync;
+}
+
+void
+rtp_jitter_buffer_set_rfc7273_sync (RTPJitterBuffer * jbuf,
+ gboolean rfc7273_sync)
+{
+ jbuf->rfc7273_sync = rfc7273_sync;
+}
+
+/**
+ * rtp_jitter_buffer_reset_skew:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Reset the skew calculations in @jbuf.
+ */
+void
+rtp_jitter_buffer_reset_skew (RTPJitterBuffer * jbuf)
+{
+ jbuf->base_time = -1;
+ jbuf->base_rtptime = -1;
+ jbuf->base_extrtp = -1;
+ jbuf->media_clock_base_time = -1;
+ jbuf->ext_rtptime = -1;
+ jbuf->last_rtptime = -1;
+ jbuf->window_pos = 0;
+ jbuf->window_filling = TRUE;
+ jbuf->window_min = 0;
+ jbuf->skew = 0;
+ jbuf->prev_send_diff = -1;
+ jbuf->prev_out_time = -1;
+ jbuf->need_resync = TRUE;
+
+ GST_DEBUG ("reset skew correction");
+}
+
+/**
+ * rtp_jitter_buffer_disable_buffering:
+ * @jbuf: an #RTPJitterBuffer
+ * @disabled: the new state
+ *
+ * Enable or disable buffering on @jbuf.
+ */
+void
+rtp_jitter_buffer_disable_buffering (RTPJitterBuffer * jbuf, gboolean disabled)
+{
+ jbuf->buffering_disabled = disabled;
+}
+
+static void
+rtp_jitter_buffer_resync (RTPJitterBuffer * jbuf, GstClockTime time,
+ GstClockTime gstrtptime, guint64 ext_rtptime, gboolean reset_skew)
+{
+ jbuf->base_time = time;
+ jbuf->media_clock_base_time = -1;
+ jbuf->base_rtptime = gstrtptime;
+ jbuf->base_extrtp = ext_rtptime;
+ jbuf->prev_out_time = -1;
+ jbuf->prev_send_diff = -1;
+ if (reset_skew) {
+ jbuf->window_filling = TRUE;
+ jbuf->window_pos = 0;
+ jbuf->window_min = 0;
+ jbuf->window_size = 0;
+ jbuf->skew = 0;
+ }
+ jbuf->need_resync = FALSE;
+}
+
+static guint64
+get_buffer_level (RTPJitterBuffer * jbuf)
+{
+ RTPJitterBufferItem *high_buf = NULL, *low_buf = NULL;
+ guint64 level;
+
+ /* first buffer with timestamp */
+ high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
+ while (high_buf) {
+ if (high_buf->dts != GST_CLOCK_TIME_NONE
+ || high_buf->pts != GST_CLOCK_TIME_NONE)
+ break;
+
+ high_buf = (RTPJitterBufferItem *) g_list_previous (high_buf);
+ }
+
+ low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+ while (low_buf) {
+ if (low_buf->dts != GST_CLOCK_TIME_NONE
+ || low_buf->pts != GST_CLOCK_TIME_NONE)
+ break;
+
+ low_buf = (RTPJitterBufferItem *) g_list_next (low_buf);
+ }
+
+ if (!high_buf || !low_buf || high_buf == low_buf) {
+ level = 0;
+ } else {
+ guint64 high_ts, low_ts;
+
+ high_ts =
+ high_buf->dts != GST_CLOCK_TIME_NONE ? high_buf->dts : high_buf->pts;
+ low_ts = low_buf->dts != GST_CLOCK_TIME_NONE ? low_buf->dts : low_buf->pts;
+
+ if (high_ts > low_ts)
+ level = high_ts - low_ts;
+ else
+ level = 0;
+
+ GST_LOG_OBJECT (jbuf,
+ "low %" GST_TIME_FORMAT " high %" GST_TIME_FORMAT " level %"
+ G_GUINT64_FORMAT, GST_TIME_ARGS (low_ts), GST_TIME_ARGS (high_ts),
+ level);
+ }
+ return level;
+}
+
+static void
+update_buffer_level (RTPJitterBuffer * jbuf, gint * percent)
+{
+ gboolean post = FALSE;
+ guint64 level;
+
+ level = get_buffer_level (jbuf);
+ GST_DEBUG ("buffer level %" GST_TIME_FORMAT, GST_TIME_ARGS (level));
+
+ if (jbuf->buffering_disabled) {
+ GST_DEBUG ("buffering is disabled");
+ level = jbuf->high_level;
+ }
+
+ if (jbuf->buffering) {
+ post = TRUE;
+ if (level >= jbuf->high_level) {
+ GST_DEBUG ("buffering finished");
+ jbuf->buffering = FALSE;
+ }
+ } else {
+ if (level < jbuf->low_level) {
+ GST_DEBUG ("buffering started");
+ jbuf->buffering = TRUE;
+ post = TRUE;
+ }
+ }
+ if (post) {
+ gint perc;
+
+ if (jbuf->buffering && (jbuf->high_level != 0)) {
+ perc = (level * 100 / jbuf->high_level);
+ perc = MIN (perc, 100);
+ } else {
+ perc = 100;
+ }
+
+ if (percent)
+ *percent = perc;
+
+ GST_DEBUG ("buffering %d", perc);
+ }
+}
+
+/* For the clock skew we use a windowed low point averaging algorithm as can be
+ * found in Fober, Orlarey and Letz, 2005, "Real Time Clock Skew Estimation
+ * over Network Delays":
+ * http://www.grame.fr/Ressources/pub/TR-050601.pdf
+ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1546
+ *
+ * The idea is that the jitter is composed of:
+ *
+ * J = N + n
+ *
+ * N : a constant network delay.
+ * n : random added noise. The noise is concentrated around 0
+ *
+ * In the receiver we can track the elapsed time at the sender with:
+ *
+ * send_diff(i) = (Tsi - Ts0);
+ *
+ * Tsi : The time at the sender at packet i
+ * Ts0 : The time at the sender at the first packet
+ *
+ * This is the difference between the RTP timestamp in the first received packet
+ * and the current packet.
+ *
+ * At the receiver we have to deal with the jitter introduced by the network.
+ *
+ * recv_diff(i) = (Tri - Tr0)
+ *
+ * Tri : The time at the receiver at packet i
+ * Tr0 : The time at the receiver at the first packet
+ *
+ * Both of these values contain a jitter Ji, a jitter for packet i, so we can
+ * write:
+ *
+ * recv_diff(i) = (Cri + D + ni) - (Cr0 + D + n0))
+ *
+ * Cri : The time of the clock at the receiver for packet i
+ * D + ni : The jitter when receiving packet i
+ *
+ * We see that the network delay is irrelevant here as we can elliminate D:
+ *
+ * recv_diff(i) = (Cri + ni) - (Cr0 + n0))
+ *
+ * The drift is now expressed as:
+ *
+ * Drift(i) = recv_diff(i) - send_diff(i);
+ *
+ * We now keep the W latest values of Drift and find the minimum (this is the
+ * one with the lowest network jitter and thus the one which is least affected
+ * by it). We average this lowest value to smooth out the resulting network skew.
+ *
+ * Both the window and the weighting used for averaging influence the accuracy
+ * of the drift estimation. Finding the correct parameters turns out to be a
+ * compromise between accuracy and inertia.
+ *
+ * We use a 2 second window or up to 512 data points, which is statistically big
+ * enough to catch spikes (FIXME, detect spikes).
+ * We also use a rather large weighting factor (125) to smoothly adapt. During
+ * startup, when filling the window, we use a parabolic weighting factor, the
+ * more the window is filled, the faster we move to the detected possible skew.
+ *
+ * Returns: @time adjusted with the clock skew.
+ */
+static GstClockTime
+calculate_skew (RTPJitterBuffer * jbuf, guint64 ext_rtptime,
+ GstClockTime gstrtptime, GstClockTime time, gint gap, gboolean is_rtx)
+{
+ guint64 send_diff, recv_diff;
+ gint64 delta;
+ gint64 old;
+ guint pos, i;
+ GstClockTime out_time;
+ guint64 slope;
+
+ /* elapsed time at sender */
+ send_diff = gstrtptime - jbuf->base_rtptime;
+
+ /* we don't have an arrival timestamp so we can't do skew detection. we
+ * should still apply a timestamp based on RTP timestamp and base_time */
+ if (time == GST_CLOCK_TIME_NONE || jbuf->base_time == GST_CLOCK_TIME_NONE
+ || is_rtx)
+ goto no_skew;
+
+ /* elapsed time at receiver, includes the jitter */
+ recv_diff = time - jbuf->base_time;
+
+ /* measure the diff */
+ delta = ((gint64) recv_diff) - ((gint64) send_diff);
+
+ /* measure the slope, this gives a rought estimate between the sender speed
+ * and the receiver speed. This should be approximately 8, higher values
+ * indicate a burst (especially when the connection starts) */
+ if (recv_diff > 0)
+ slope = (send_diff * 8) / recv_diff;
+ else
+ slope = 8;
+
+ GST_DEBUG ("time %" GST_TIME_FORMAT ", base %" GST_TIME_FORMAT ", recv_diff %"
+ GST_TIME_FORMAT ", slope %" G_GUINT64_FORMAT, GST_TIME_ARGS (time),
+ GST_TIME_ARGS (jbuf->base_time), GST_TIME_ARGS (recv_diff), slope);
+
+ /* if the difference between the sender timeline and the receiver timeline
+ * changed too quickly we have to resync because the server likely restarted
+ * its timestamps. */
+ if (ABS (delta - jbuf->skew) > GST_SECOND) {
+ GST_WARNING ("delta - skew: %" GST_TIME_FORMAT " too big, reset skew",
+ GST_TIME_ARGS (ABS (delta - jbuf->skew)));
+ rtp_jitter_buffer_resync (jbuf, time, gstrtptime, ext_rtptime, TRUE);
+ send_diff = 0;
+ delta = 0;
+ gap = 0;
+ }
+
+ /* only do skew calculations if we didn't have a gap. if too much time
+ * has elapsed despite there being a gap, we resynced already. */
+ if (G_UNLIKELY (gap != 0))
+ goto no_skew;
+
+ pos = jbuf->window_pos;
+
+ if (G_UNLIKELY (jbuf->window_filling)) {
+ /* we are filling the window */
+ GST_DEBUG ("filling %d, delta %" G_GINT64_FORMAT, pos, delta);
+ jbuf->window[pos++] = delta;
+ /* calc the min delta we observed */
+ if (G_UNLIKELY (pos == 1 || delta < jbuf->window_min))
+ jbuf->window_min = delta;
+
+ if (G_UNLIKELY (send_diff >= MAX_TIME || pos >= MAX_WINDOW)) {
+ jbuf->window_size = pos;
+
+ /* window filled */
+ GST_DEBUG ("min %" G_GINT64_FORMAT, jbuf->window_min);
+
+ /* the skew is now the min */
+ jbuf->skew = jbuf->window_min;
+ jbuf->window_filling = FALSE;
+ } else {
+ gint perc_time, perc_window, perc;
+
+ /* figure out how much we filled the window, this depends on the amount of
+ * time we have or the max number of points we keep. */
+ perc_time = send_diff * 100 / MAX_TIME;
+ perc_window = pos * 100 / MAX_WINDOW;
+ perc = MAX (perc_time, perc_window);
+
+ /* make a parabolic function, the closer we get to the MAX, the more value
+ * we give to the scaling factor of the new value */
+ perc = perc * perc;
+
+ /* quickly go to the min value when we are filling up, slowly when we are
+ * just starting because we're not sure it's a good value yet. */
+ jbuf->skew =
+ (perc * jbuf->window_min + ((10000 - perc) * jbuf->skew)) / 10000;
+ jbuf->window_size = pos + 1;
+ }
+ } else {
+ /* pick old value and store new value. We keep the previous value in order
+ * to quickly check if the min of the window changed */
+ old = jbuf->window[pos];
+ jbuf->window[pos++] = delta;
+
+ if (G_UNLIKELY (delta <= jbuf->window_min)) {
+ /* if the new value we inserted is smaller or equal to the current min,
+ * it becomes the new min */
+ jbuf->window_min = delta;
+ } else if (G_UNLIKELY (old == jbuf->window_min)) {
+ gint64 min = G_MAXINT64;
+
+ /* if we removed the old min, we have to find a new min */
+ for (i = 0; i < jbuf->window_size; i++) {
+ /* we found another value equal to the old min, we can stop searching now */
+ if (jbuf->window[i] == old) {
+ min = old;
+ break;
+ }
+ if (jbuf->window[i] < min)
+ min = jbuf->window[i];
+ }
+ jbuf->window_min = min;
+ }
+ /* average the min values */
+ jbuf->skew = (jbuf->window_min + (124 * jbuf->skew)) / 125;
+ GST_DEBUG ("delta %" G_GINT64_FORMAT ", new min: %" G_GINT64_FORMAT,
+ delta, jbuf->window_min);
+ }
+ /* wrap around in the window */
+ if (G_UNLIKELY (pos >= jbuf->window_size))
+ pos = 0;
+ jbuf->window_pos = pos;
+
+no_skew:
+ /* the output time is defined as the base timestamp plus the RTP time
+ * adjusted for the clock skew .*/
+ if (jbuf->base_time != GST_CLOCK_TIME_NONE) {
+ out_time = jbuf->base_time + send_diff;
+ /* skew can be negative and we don't want to make invalid timestamps */
+ if (jbuf->skew < 0 && out_time < (GstClockTime) - jbuf->skew) {
+ out_time = 0;
+ } else {
+ out_time += jbuf->skew;
+ }
+ } else
+ out_time = -1;
+
+ GST_DEBUG ("skew %" G_GINT64_FORMAT ", out %" GST_TIME_FORMAT,
+ jbuf->skew, GST_TIME_ARGS (out_time));
+
+ return out_time;
+}
+
+static void
+queue_do_insert (RTPJitterBuffer * jbuf, GList * list, GList * item)
+{
+ GQueue *queue = jbuf->packets;
+
+ /* It's more likely that the packet was inserted at the tail of the queue */
+ if (G_LIKELY (list)) {
+ item->prev = list;
+ item->next = list->next;
+ list->next = item;
+ } else {
+ item->prev = NULL;
+ item->next = queue->head;
+ queue->head = item;
+ }
+ if (item->next)
+ item->next->prev = item;
+ else
+ queue->tail = item;
+ queue->length++;
+}
+
+GstClockTime
+rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts,
+ gboolean estimated_dts, guint32 rtptime, GstClockTime base_time,
+ gint gap, gboolean is_rtx)
+{
+ guint64 ext_rtptime;
+ GstClockTime gstrtptime, pts;
+ GstClock *media_clock, *pipeline_clock;
+ guint64 media_clock_offset;
+ gboolean rfc7273_mode;
+
+ /* rtp time jumps are checked for during skew calculation, but bypassed
+ * in other mode, so mind those here and reset jb if needed.
+ * Only reset if valid input time, which is likely for UDP input
+ * where we expect this might happen due to async thread effects
+ * (in seek and state change cycles), but not so much for TCP input */
+ if (GST_CLOCK_TIME_IS_VALID (dts) && !estimated_dts &&
+ jbuf->mode != RTP_JITTER_BUFFER_MODE_SLAVE &&
+ jbuf->base_time != GST_CLOCK_TIME_NONE
+ && jbuf->last_rtptime != GST_CLOCK_TIME_NONE) {
+ GstClockTime ext_rtptime = jbuf->ext_rtptime;
+
+ ext_rtptime = gst_rtp_buffer_ext_timestamp (&ext_rtptime, rtptime);
+ if (ext_rtptime > jbuf->last_rtptime + 3 * jbuf->clock_rate ||
+ ext_rtptime + 3 * jbuf->clock_rate < jbuf->last_rtptime) {
+ if (!is_rtx) {
+ /* reset even if we don't have valid incoming time;
+ * still better than producing possibly very bogus output timestamp */
+ GST_WARNING ("rtp delta too big, reset skew");
+ rtp_jitter_buffer_reset_skew (jbuf);
+ } else {
+ GST_WARNING ("rtp delta too big: ignore rtx packet");
+ media_clock = NULL;
+ pipeline_clock = NULL;
+ pts = GST_CLOCK_TIME_NONE;
+ goto done;
+ }
+ }
+ }
+
+ /* Return the last time if we got the same RTP timestamp again */
+ ext_rtptime = gst_rtp_buffer_ext_timestamp (&jbuf->ext_rtptime, rtptime);
+ if (jbuf->last_rtptime != GST_CLOCK_TIME_NONE
+ && ext_rtptime == jbuf->last_rtptime) {
+ return jbuf->prev_out_time;
+ }
+
+ /* keep track of the last extended rtptime */
+ jbuf->last_rtptime = ext_rtptime;
+
+ g_mutex_lock (&jbuf->clock_lock);
+ media_clock = jbuf->media_clock ? gst_object_ref (jbuf->media_clock) : NULL;
+ pipeline_clock =
+ jbuf->pipeline_clock ? gst_object_ref (jbuf->pipeline_clock) : NULL;
+ media_clock_offset = jbuf->media_clock_offset;
+ g_mutex_unlock (&jbuf->clock_lock);
+
+ gstrtptime =
+ gst_util_uint64_scale_int (ext_rtptime, GST_SECOND, jbuf->clock_rate);
+
+ if (G_LIKELY (jbuf->base_rtptime != GST_CLOCK_TIME_NONE)) {
+ /* check elapsed time in RTP units */
+ if (gstrtptime < jbuf->base_rtptime) {
+ if (!is_rtx) {
+ /* elapsed time at sender, timestamps can go backwards and thus be
+ * smaller than our base time, schedule to take a new base time in
+ * that case. */
+ GST_WARNING ("backward timestamps at server, schedule resync");
+ jbuf->need_resync = TRUE;
+ } else {
+ GST_WARNING ("backward timestamps: ignore rtx packet");
+ pts = GST_CLOCK_TIME_NONE;
+ goto done;
+ }
+ }
+ }
+
+ switch (jbuf->mode) {
+ case RTP_JITTER_BUFFER_MODE_NONE:
+ case RTP_JITTER_BUFFER_MODE_BUFFER:
+ /* send 0 as the first timestamp and -1 for the other ones. This will
+ * interpolate them from the RTP timestamps with a 0 origin. In buffering
+ * mode we will adjust the outgoing timestamps according to the amount of
+ * time we spent buffering. */
+ if (jbuf->base_time == GST_CLOCK_TIME_NONE)
+ dts = 0;
+ else
+ dts = -1;
+ break;
+ case RTP_JITTER_BUFFER_MODE_SYNCED:
+ /* synchronized clocks, take first timestamp as base, use RTP timestamps
+ * to interpolate */
+ if (jbuf->base_time != GST_CLOCK_TIME_NONE && !jbuf->need_resync)
+ dts = -1;
+ break;
+ case RTP_JITTER_BUFFER_MODE_SLAVE:
+ default:
+ break;
+ }
+
+ /* need resync, lock on to time and gstrtptime if we can, otherwise we
+ * do with the previous values */
+ if (G_UNLIKELY (jbuf->need_resync && dts != GST_CLOCK_TIME_NONE)) {
+ if (is_rtx) {
+ GST_DEBUG ("not resyncing on rtx packet, discard");
+ pts = GST_CLOCK_TIME_NONE;
+ goto done;
+ }
+ GST_INFO ("resync to time %" GST_TIME_FORMAT ", rtptime %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (dts), GST_TIME_ARGS (gstrtptime));
+ rtp_jitter_buffer_resync (jbuf, dts, gstrtptime, ext_rtptime, FALSE);
+ }
+
+ GST_DEBUG ("extrtp %" G_GUINT64_FORMAT ", gstrtp %" GST_TIME_FORMAT ", base %"
+ GST_TIME_FORMAT ", send_diff %" GST_TIME_FORMAT, ext_rtptime,
+ GST_TIME_ARGS (gstrtptime), GST_TIME_ARGS (jbuf->base_rtptime),
+ GST_TIME_ARGS (gstrtptime - jbuf->base_rtptime));
+
+ rfc7273_mode = media_clock && pipeline_clock
+ && gst_clock_is_synced (media_clock);
+
+ if (rfc7273_mode && jbuf->mode == RTP_JITTER_BUFFER_MODE_SLAVE
+ && (media_clock_offset == GST_CLOCK_TIME_NONE || !jbuf->rfc7273_sync)) {
+ GstClockTime internal, external;
+ GstClockTime rate_num, rate_denom;
+ GstClockTime nsrtptimediff, rtpntptime, rtpsystime;
+
+ gst_clock_get_calibration (media_clock, &internal, &external, &rate_num,
+ &rate_denom);
+
+ /* Slave to the RFC7273 media clock instead of trying to estimate it
+ * based on receive times and RTP timestamps */
+
+ if (jbuf->media_clock_base_time == GST_CLOCK_TIME_NONE) {
+ if (jbuf->base_time != GST_CLOCK_TIME_NONE) {
+ jbuf->media_clock_base_time =
+ gst_clock_unadjust_with_calibration (media_clock,
+ jbuf->base_time + base_time, internal, external, rate_num,
+ rate_denom);
+ } else {
+ if (dts != GST_CLOCK_TIME_NONE)
+ jbuf->media_clock_base_time =
+ gst_clock_unadjust_with_calibration (media_clock, dts + base_time,
+ internal, external, rate_num, rate_denom);
+ else
+ jbuf->media_clock_base_time =
+ gst_clock_get_internal_time (media_clock);
+ jbuf->base_rtptime = gstrtptime;
+ }
+ }
+
+ if (gstrtptime > jbuf->base_rtptime)
+ nsrtptimediff = gstrtptime - jbuf->base_rtptime;
+ else
+ nsrtptimediff = 0;
+
+ rtpntptime = nsrtptimediff + jbuf->media_clock_base_time;
+
+ rtpsystime =
+ gst_clock_adjust_with_calibration (media_clock, rtpntptime, internal,
+ external, rate_num, rate_denom);
+
+ if (rtpsystime > base_time)
+ pts = rtpsystime - base_time;
+ else
+ pts = 0;
+
+ GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts));
+ } else if (rfc7273_mode && (jbuf->mode == RTP_JITTER_BUFFER_MODE_SLAVE
+ || jbuf->mode == RTP_JITTER_BUFFER_MODE_SYNCED)
+ && media_clock_offset != GST_CLOCK_TIME_NONE && jbuf->rfc7273_sync) {
+ GstClockTime ntptime, rtptime_tmp;
+ GstClockTime ntprtptime, rtpsystime;
+ GstClockTime internal, external;
+ GstClockTime rate_num, rate_denom;
+
+ /* Don't do any of the dts related adjustments further down */
+ dts = -1;
+
+ /* Calculate the actual clock time on the sender side based on the
+ * RFC7273 clock and convert it to our pipeline clock
+ */
+
+ gst_clock_get_calibration (media_clock, &internal, &external, &rate_num,
+ &rate_denom);
+
+ ntptime = gst_clock_get_internal_time (media_clock);
+
+ ntprtptime = gst_util_uint64_scale (ntptime, jbuf->clock_rate, GST_SECOND);
+ ntprtptime += media_clock_offset;
+ ntprtptime &= 0xffffffff;
+
+ rtptime_tmp = rtptime;
+ /* Check for wraparounds, we assume that the diff between current RTP
+ * timestamp and current media clock time can't be bigger than
+ * 2**31 clock units */
+ if (ntprtptime > rtptime_tmp && ntprtptime - rtptime_tmp >= 0x80000000)
+ rtptime_tmp += G_GUINT64_CONSTANT (0x100000000);
+ else if (rtptime_tmp > ntprtptime && rtptime_tmp - ntprtptime >= 0x80000000)
+ ntprtptime += G_GUINT64_CONSTANT (0x100000000);
+
+ if (ntprtptime > rtptime_tmp)
+ ntptime -=
+ gst_util_uint64_scale (ntprtptime - rtptime_tmp, jbuf->clock_rate,
+ GST_SECOND);
+ else
+ ntptime +=
+ gst_util_uint64_scale (rtptime_tmp - ntprtptime, jbuf->clock_rate,
+ GST_SECOND);
+
+ rtpsystime =
+ gst_clock_adjust_with_calibration (media_clock, ntptime, internal,
+ external, rate_num, rate_denom);
+ /* All this assumes that the pipeline has enough additional
+ * latency to cover for the network delay */
+ if (rtpsystime > base_time)
+ pts = rtpsystime - base_time;
+ else
+ pts = 0;
+
+ GST_DEBUG ("RFC7273 clock time %" GST_TIME_FORMAT ", out %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (rtpsystime), GST_TIME_ARGS (pts));
+ } else {
+ /* If we used the RFC7273 clock before and not anymore,
+ * we need to resync it later again */
+ jbuf->media_clock_base_time = -1;
+
+ /* do skew calculation by measuring the difference between rtptime and the
+ * receive dts, this function will return the skew corrected rtptime. */
+ pts = calculate_skew (jbuf, ext_rtptime, gstrtptime, dts, gap, is_rtx);
+ }
+
+ /* check if timestamps are not going backwards, we can only check this if we
+ * have a previous out time and a previous send_diff */
+ if (G_LIKELY (pts != GST_CLOCK_TIME_NONE
+ && jbuf->prev_out_time != GST_CLOCK_TIME_NONE
+ && jbuf->prev_send_diff != -1)) {
+ /* now check for backwards timestamps */
+ if (G_UNLIKELY (
+ /* if the server timestamps went up and the out_time backwards */
+ ((gint64) (gstrtptime - jbuf->base_rtptime) > jbuf->prev_send_diff
+ && pts < jbuf->prev_out_time) ||
+ /* if the server timestamps went backwards and the out_time forwards */
+ ((gint64) (gstrtptime - jbuf->base_rtptime) < jbuf->prev_send_diff
+ && pts > jbuf->prev_out_time) ||
+ /* if the server timestamps did not change */
+ (gint64) (gstrtptime - jbuf->base_rtptime) == jbuf->prev_send_diff)) {
+ GST_DEBUG ("backwards timestamps, using previous time");
+ pts = jbuf->prev_out_time;
+ }
+ }
+
+ if (gap == 0 && dts != GST_CLOCK_TIME_NONE && pts + jbuf->delay < dts) {
+ /* if we are going to produce a timestamp that is later than the input
+ * timestamp, we need to reset the jitterbuffer. Likely the server paused
+ * temporarily */
+ GST_DEBUG ("out %" GST_TIME_FORMAT " + %" G_GUINT64_FORMAT " < time %"
+ GST_TIME_FORMAT ", reset jitterbuffer and discard", GST_TIME_ARGS (pts),
+ jbuf->delay, GST_TIME_ARGS (dts));
+ rtp_jitter_buffer_reset_skew (jbuf);
+ pts = GST_CLOCK_TIME_NONE;
+ goto done;
+ }
+
+ jbuf->prev_out_time = pts;
+ jbuf->prev_send_diff = gstrtptime - jbuf->base_rtptime;
+
+done:
+ if (media_clock)
+ gst_object_unref (media_clock);
+ if (pipeline_clock)
+ gst_object_unref (pipeline_clock);
+
+ return pts;
+}
+
+
+/**
+ * rtp_jitter_buffer_insert:
+ * @jbuf: an #RTPJitterBuffer
+ * @item: an #RTPJitterBufferItem to insert
+ * @head: TRUE when the head element changed.
+ * @percent: the buffering percent after insertion
+ *
+ * Inserts @item into the packet queue of @jbuf. The sequence number of the
+ * packet will be used to sort the packets. This function takes ownerhip of
+ * @buf when the function returns %TRUE.
+ *
+ * When @head is %TRUE, the new packet was added at the head of the queue and
+ * will be available with the next call to rtp_jitter_buffer_pop() and
+ * rtp_jitter_buffer_peek().
+ *
+ * Returns: %FALSE if a packet with the same number already existed.
+ */
+gboolean
+rtp_jitter_buffer_insert (RTPJitterBuffer * jbuf, RTPJitterBufferItem * item,
+ gboolean * head, gint * percent)
+{
+ GList *list, *event = NULL;
+ guint16 seqnum;
+
+ g_return_val_if_fail (jbuf != NULL, FALSE);
+ g_return_val_if_fail (item != NULL, FALSE);
+
+ list = jbuf->packets->tail;
+
+ /* no seqnum, simply append then */
+ if (item->seqnum == G_MAXUINT)
+ goto append;
+
+ seqnum = item->seqnum;
+
+ /* loop the list to skip strictly larger seqnum buffers */
+ for (; list; list = g_list_previous (list)) {
+ guint16 qseq;
+ gint gap;
+ RTPJitterBufferItem *qitem = (RTPJitterBufferItem *) list;
+
+ if (qitem->seqnum == G_MAXUINT) {
+ /* keep a pointer to the first consecutive event if not already
+ * set. we will insert the packet after the event if we can't find
+ * a packet with lower sequence number before the event. */
+ if (event == NULL)
+ event = list;
+ continue;
+ }
+
+ qseq = qitem->seqnum;
+
+ /* compare the new seqnum to the one in the buffer */
+ gap = gst_rtp_buffer_compare_seqnum (seqnum, qseq);
+
+ /* we hit a packet with the same seqnum, notify a duplicate */
+ if (G_UNLIKELY (gap == 0))
+ goto duplicate;
+
+ /* seqnum > qseq, we can stop looking */
+ if (G_LIKELY (gap < 0))
+ break;
+
+ /* if we've found a packet with greater sequence number, cleanup the
+ * event pointer as the packet will be inserted before the event */
+ event = NULL;
+ }
+
+ /* if event is set it means that packets before the event had smaller
+ * sequence number, so we will insert our packet after the event */
+ if (event)
+ list = event;
+
+append:
+ queue_do_insert (jbuf, list, (GList *) item);
+
+ /* buffering mode, update buffer stats */
+ if (jbuf->mode == RTP_JITTER_BUFFER_MODE_BUFFER)
+ update_buffer_level (jbuf, percent);
+ else if (percent)
+ *percent = -1;
+
+ /* head was changed when we did not find a previous packet, we set the return
+ * flag when requested. */
+ if (G_LIKELY (head))
+ *head = (list == NULL);
+
+ return TRUE;
+
+ /* ERRORS */
+duplicate:
+ {
+ GST_DEBUG ("duplicate packet %d found", (gint) seqnum);
+ if (G_LIKELY (head))
+ *head = FALSE;
+ return FALSE;
+ }
+}
+
+/**
+ * rtp_jitter_buffer_pop:
+ * @jbuf: an #RTPJitterBuffer
+ * @percent: the buffering percent
+ *
+ * Pops the oldest buffer from the packet queue of @jbuf. The popped buffer will
+ * have its timestamp adjusted with the incoming running_time and the detected
+ * clock skew.
+ *
+ * Returns: a #GstBuffer or %NULL when there was no packet in the queue.
+ */
+RTPJitterBufferItem *
+rtp_jitter_buffer_pop (RTPJitterBuffer * jbuf, gint * percent)
+{
+ GList *item = NULL;
+ GQueue *queue;
+
+ g_return_val_if_fail (jbuf != NULL, NULL);
+
+ queue = jbuf->packets;
+
+ item = queue->head;
+ if (item) {
+ queue->head = item->next;
+ if (queue->head)
+ queue->head->prev = NULL;
+ else
+ queue->tail = NULL;
+ queue->length--;
+ }
+
+ /* buffering mode, update buffer stats */
+ if (jbuf->mode == RTP_JITTER_BUFFER_MODE_BUFFER)
+ update_buffer_level (jbuf, percent);
+ else if (percent)
+ *percent = -1;
+
+ return (RTPJitterBufferItem *) item;
+}
+
+/**
+ * rtp_jitter_buffer_peek:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Peek the oldest buffer from the packet queue of @jbuf.
+ *
+ * See rtp_jitter_buffer_insert() to check when an older packet was
+ * added.
+ *
+ * Returns: a #GstBuffer or %NULL when there was no packet in the queue.
+ */
+RTPJitterBufferItem *
+rtp_jitter_buffer_peek (RTPJitterBuffer * jbuf)
+{
+ g_return_val_if_fail (jbuf != NULL, NULL);
+
+ return (RTPJitterBufferItem *) jbuf->packets->head;
+}
+
+/**
+ * rtp_jitter_buffer_flush:
+ * @jbuf: an #RTPJitterBuffer
+ * @free_func: function to free each item
+ * @user_data: user data passed to @free_func
+ *
+ * Flush all packets from the jitterbuffer.
+ */
+void
+rtp_jitter_buffer_flush (RTPJitterBuffer * jbuf, GFunc free_func,
+ gpointer user_data)
+{
+ GList *item;
+
+ g_return_if_fail (jbuf != NULL);
+ g_return_if_fail (free_func != NULL);
+
+ while ((item = g_queue_pop_head_link (jbuf->packets)))
+ free_func ((RTPJitterBufferItem *) item, user_data);
+}
+
+/**
+ * rtp_jitter_buffer_is_buffering:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Check if @jbuf is buffering currently. Users of the jitterbuffer should not
+ * pop packets while in buffering mode.
+ *
+ * Returns: the buffering state of @jbuf
+ */
+gboolean
+rtp_jitter_buffer_is_buffering (RTPJitterBuffer * jbuf)
+{
+ return jbuf->buffering && !jbuf->buffering_disabled;
+}
+
+/**
+ * rtp_jitter_buffer_set_buffering:
+ * @jbuf: an #RTPJitterBuffer
+ * @buffering: the new buffering state
+ *
+ * Forces @jbuf to go into the buffering state.
+ */
+void
+rtp_jitter_buffer_set_buffering (RTPJitterBuffer * jbuf, gboolean buffering)
+{
+ jbuf->buffering = buffering;
+}
+
+/**
+ * rtp_jitter_buffer_get_percent:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the buffering percent of the jitterbuffer.
+ *
+ * Returns: the buffering percent
+ */
+gint
+rtp_jitter_buffer_get_percent (RTPJitterBuffer * jbuf)
+{
+ gint percent;
+ guint64 level;
+
+ if (G_UNLIKELY (jbuf->high_level == 0))
+ return 100;
+
+ if (G_UNLIKELY (jbuf->buffering_disabled))
+ return 100;
+
+ level = get_buffer_level (jbuf);
+ percent = (level * 100 / jbuf->high_level);
+ percent = MIN (percent, 100);
+
+ return percent;
+}
+
+/**
+ * rtp_jitter_buffer_num_packets:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the number of packets currently in "jbuf.
+ *
+ * Returns: The number of packets in @jbuf.
+ */
+guint
+rtp_jitter_buffer_num_packets (RTPJitterBuffer * jbuf)
+{
+ g_return_val_if_fail (jbuf != NULL, 0);
+
+ return jbuf->packets->length;
+}
+
+/**
+ * rtp_jitter_buffer_get_ts_diff:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the difference between the timestamps of first and last packet in the
+ * jitterbuffer.
+ *
+ * Returns: The difference expressed in the timestamp units of the packets.
+ */
+guint32
+rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer * jbuf)
+{
+ guint64 high_ts, low_ts;
+ RTPJitterBufferItem *high_buf, *low_buf;
+ guint32 result;
+
+ g_return_val_if_fail (jbuf != NULL, 0);
+
+ high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
+ low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+
+ if (!high_buf || !low_buf || high_buf == low_buf)
+ return 0;
+
+ high_ts = high_buf->rtptime;
+ low_ts = low_buf->rtptime;
+
+ /* it needs to work if ts wraps */
+ if (high_ts >= low_ts) {
+ result = (guint32) (high_ts - low_ts);
+ } else {
+ result = (guint32) (high_ts + G_MAXUINT32 + 1 - low_ts);
+ }
+ return result;
+}
+
+
+/*
+ * rtp_jitter_buffer_get_seqnum_diff:
+ * @jbuf: an #RTPJitterBuffer
+ *
+ * Get the difference between the seqnum of first and last packet in the
+ * jitterbuffer.
+ *
+ * Returns: The difference expressed in seqnum.
+ */
+static guint16
+rtp_jitter_buffer_get_seqnum_diff (RTPJitterBuffer * jbuf)
+{
+ guint32 high_seqnum, low_seqnum;
+ RTPJitterBufferItem *high_buf, *low_buf;
+ guint16 result;
+
+ g_return_val_if_fail (jbuf != NULL, 0);
+
+ high_buf = (RTPJitterBufferItem *) g_queue_peek_tail_link (jbuf->packets);
+ low_buf = (RTPJitterBufferItem *) g_queue_peek_head_link (jbuf->packets);
+
+ while (high_buf && high_buf->seqnum == G_MAXUINT)
+ high_buf = (RTPJitterBufferItem *) high_buf->prev;
+
+ while (low_buf && low_buf->seqnum == G_MAXUINT)
+ low_buf = (RTPJitterBufferItem *) low_buf->next;
+
+ if (!high_buf || !low_buf || high_buf == low_buf)
+ return 0;
+
+ high_seqnum = high_buf->seqnum;
+ low_seqnum = low_buf->seqnum;
+
+ /* it needs to work if ts wraps */
+ if (high_seqnum >= low_seqnum) {
+ result = (guint32) (high_seqnum - low_seqnum);
+ } else {
+ result = (guint32) (high_seqnum + G_MAXUINT16 + 1 - low_seqnum);
+ }
+ return result;
+}
+
+/**
+ * rtp_jitter_buffer_get_sync:
+ * @jbuf: an #RTPJitterBuffer
+ * @rtptime: result RTP time
+ * @timestamp: result GStreamer timestamp
+ * @clock_rate: clock-rate of @rtptime
+ * @last_rtptime: last seen rtptime.
+ *
+ * Calculates the relation between the RTP timestamp and the GStreamer timestamp
+ * used for constructing timestamps.
+ *
+ * For extended RTP timestamp @rtptime with a clock-rate of @clock_rate,
+ * the GStreamer timestamp is currently @timestamp.
+ *
+ * The last seen extended RTP timestamp with clock-rate @clock-rate is returned in
+ * @last_rtptime.
+ */
+void
+rtp_jitter_buffer_get_sync (RTPJitterBuffer * jbuf, guint64 * rtptime,
+ guint64 * timestamp, guint32 * clock_rate, guint64 * last_rtptime)
+{
+ if (rtptime)
+ *rtptime = jbuf->base_extrtp;
+ if (timestamp)
+ *timestamp = jbuf->base_time + jbuf->skew;
+ if (clock_rate)
+ *clock_rate = jbuf->clock_rate;
+ if (last_rtptime)
+ *last_rtptime = jbuf->last_rtptime;
+}
+
+/**
+ * rtp_jitter_buffer_can_fast_start:
+ * @jbuf: an #RTPJitterBuffer
+ * @num_packets: Number of consecutive packets needed
+ *
+ * Check if in the queue if there is enough packets with consecutive seqnum in
+ * order to start delivering them.
+ *
+ * Returns: %TRUE if the required number of consecutive packets was found.
+ */
+gboolean
+rtp_jitter_buffer_can_fast_start (RTPJitterBuffer * jbuf, gint num_packet)
+{
+ gboolean ret = TRUE;
+ RTPJitterBufferItem *last_item = NULL, *item;
+ gint i;
+
+ if (rtp_jitter_buffer_num_packets (jbuf) < (guint) num_packet)
+ return FALSE;
+
+ item = rtp_jitter_buffer_peek (jbuf);
+ for (i = 0; i < num_packet; i++) {
+ if (G_LIKELY (last_item)) {
+ guint16 expected_seqnum = last_item->seqnum + 1;
+
+ if (expected_seqnum != item->seqnum) {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ last_item = item;
+ item = (RTPJitterBufferItem *) last_item->next;
+ }
+
+ return ret;
+}
+
+gboolean
+rtp_jitter_buffer_is_full (RTPJitterBuffer * jbuf)
+{
+ return rtp_jitter_buffer_get_seqnum_diff (jbuf) >= 32765 &&
+ rtp_jitter_buffer_num_packets (jbuf) > 10000;
+}
+
+void
+rtp_jitter_buffer_find_earliest (RTPJitterBuffer * jbuf, GstClockTime * pts,
+ guint * seqnum)
+{
+ GList *tmp;
+ RTPJitterBufferItem *earliest = NULL;
+
+ *pts = GST_CLOCK_TIME_NONE;
+ *seqnum = 0;
+
+ for (tmp = jbuf->packets->head; tmp; tmp = tmp->next) {
+ RTPJitterBufferItem *item = (RTPJitterBufferItem *) tmp;
+
+ if (!earliest || item->pts <= earliest->pts)
+ earliest = item;
+ }
+
+ if (earliest) {
+ *pts = earliest->pts;
+ *seqnum = earliest->seqnum;
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.h b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.h
new file mode 100644
index 000000000..31fbb747a
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpjitterbuffer.h
@@ -0,0 +1,201 @@
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __RTP_JITTER_BUFFER_H__
+#define __RTP_JITTER_BUFFER_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/gstrtcpbuffer.h>
+
+typedef struct _RTPJitterBuffer RTPJitterBuffer;
+typedef struct _RTPJitterBufferClass RTPJitterBufferClass;
+typedef struct _RTPJitterBufferItem RTPJitterBufferItem;
+
+#define RTP_TYPE_JITTER_BUFFER (rtp_jitter_buffer_get_type())
+#define RTP_JITTER_BUFFER(src) (G_TYPE_CHECK_INSTANCE_CAST((src),RTP_TYPE_JITTER_BUFFER,RTPJitterBuffer))
+#define RTP_JITTER_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),RTP_TYPE_JITTER_BUFFER,RTPJitterBufferClass))
+#define RTP_IS_JITTER_BUFFER(src) (G_TYPE_CHECK_INSTANCE_TYPE((src),RTP_TYPE_JITTER_BUFFER))
+#define RTP_IS_JITTER_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),RTP_TYPE_JITTER_BUFFER))
+#define RTP_JITTER_BUFFER_CAST(src) ((RTPJitterBuffer *)(src))
+
+/**
+ * RTPJitterBufferMode:
+ * @RTP_JITTER_BUFFER_MODE_NONE: don't do any skew correction, outgoing
+ * timestamps are calculated directly from the RTP timestamps. This mode is
+ * good for recording but not for real-time applications.
+ * @RTP_JITTER_BUFFER_MODE_SLAVE: calculate the skew between sender and receiver
+ * and produce smoothed adjusted outgoing timestamps. This mode is good for
+ * low latency communications.
+ * @RTP_JITTER_BUFFER_MODE_BUFFER: buffer packets between low/high watermarks.
+ * This mode is good for streaming communication.
+ * @RTP_JITTER_BUFFER_MODE_SYNCED: sender and receiver clocks are synchronized,
+ * like #RTP_JITTER_BUFFER_MODE_SLAVE but skew is assumed to be 0. Good for
+ * low latency communication when sender and receiver clocks are
+ * synchronized and there is thus no clock skew.
+ * @RTP_JITTER_BUFFER_MODE_LAST: last buffer mode.
+ *
+ * The different buffer modes for a jitterbuffer.
+ */
+typedef enum {
+ RTP_JITTER_BUFFER_MODE_NONE = 0,
+ RTP_JITTER_BUFFER_MODE_SLAVE = 1,
+ RTP_JITTER_BUFFER_MODE_BUFFER = 2,
+ /* FIXME 3 is missing because it was used for 'auto' in jitterbuffer */
+ RTP_JITTER_BUFFER_MODE_SYNCED = 4,
+ RTP_JITTER_BUFFER_MODE_LAST
+} RTPJitterBufferMode;
+
+#define RTP_TYPE_JITTER_BUFFER_MODE (rtp_jitter_buffer_mode_get_type())
+GType rtp_jitter_buffer_mode_get_type (void);
+
+#define RTP_JITTER_BUFFER_MAX_WINDOW 512
+/**
+ * RTPJitterBuffer:
+ *
+ * A JitterBuffer in the #RTPSession
+ */
+struct _RTPJitterBuffer {
+ GObject object;
+
+ GQueue *packets;
+
+ RTPJitterBufferMode mode;
+
+ GstClockTime delay;
+
+ /* for buffering */
+ gboolean buffering;
+ guint64 low_level;
+ guint64 high_level;
+
+ /* for calculating skew */
+ gboolean need_resync;
+ GstClockTime base_time;
+ GstClockTime base_rtptime;
+ GstClockTime media_clock_base_time;
+ guint32 clock_rate;
+ GstClockTime base_extrtp;
+ GstClockTime prev_out_time;
+ guint64 ext_rtptime;
+ guint64 last_rtptime;
+ gint64 window[RTP_JITTER_BUFFER_MAX_WINDOW];
+ guint window_pos;
+ guint window_size;
+ gboolean window_filling;
+ gint64 window_min;
+ gint64 skew;
+ gint64 prev_send_diff;
+ gboolean buffering_disabled;
+
+ GMutex clock_lock;
+ GstClock *pipeline_clock;
+ GstClock *media_clock;
+ gulong media_clock_synced_id;
+ guint64 media_clock_offset;
+
+ gboolean rfc7273_sync;
+};
+
+struct _RTPJitterBufferClass {
+ GObjectClass parent_class;
+};
+
+/**
+ * RTPJitterBufferItem:
+ * @data: the data of the item
+ * @next: pointer to next item
+ * @prev: pointer to previous item
+ * @type: the type of @data, used freely by caller
+ * @dts: input DTS
+ * @pts: output PTS
+ * @seqnum: seqnum, the seqnum is used to insert the item in the
+ * right position in the jitterbuffer and detect duplicates. Use -1 to
+ * append.
+ * @count: amount of seqnum in this item
+ * @rtptime: rtp timestamp
+ *
+ * An object containing an RTP packet or event.
+ */
+struct _RTPJitterBufferItem {
+ gpointer data;
+ GList *next;
+ GList *prev;
+ guint type;
+ GstClockTime dts;
+ GstClockTime pts;
+ guint seqnum;
+ guint count;
+ guint rtptime;
+};
+
+GType rtp_jitter_buffer_get_type (void);
+
+/* managing lifetime */
+RTPJitterBuffer* rtp_jitter_buffer_new (void);
+
+RTPJitterBufferMode rtp_jitter_buffer_get_mode (RTPJitterBuffer *jbuf);
+void rtp_jitter_buffer_set_mode (RTPJitterBuffer *jbuf, RTPJitterBufferMode mode);
+
+GstClockTime rtp_jitter_buffer_get_delay (RTPJitterBuffer *jbuf);
+void rtp_jitter_buffer_set_delay (RTPJitterBuffer *jbuf, GstClockTime delay);
+
+void rtp_jitter_buffer_set_clock_rate (RTPJitterBuffer *jbuf, guint32 clock_rate);
+guint32 rtp_jitter_buffer_get_clock_rate (RTPJitterBuffer *jbuf);
+
+void rtp_jitter_buffer_set_media_clock (RTPJitterBuffer *jbuf, GstClock * clock, guint64 clock_offset);
+void rtp_jitter_buffer_set_pipeline_clock (RTPJitterBuffer *jbuf, GstClock * clock);
+
+gboolean rtp_jitter_buffer_get_rfc7273_sync (RTPJitterBuffer *jbuf);
+void rtp_jitter_buffer_set_rfc7273_sync (RTPJitterBuffer *jbuf, gboolean rfc7273_sync);
+
+void rtp_jitter_buffer_reset_skew (RTPJitterBuffer *jbuf);
+
+gboolean rtp_jitter_buffer_insert (RTPJitterBuffer *jbuf,
+ RTPJitterBufferItem *item,
+ gboolean *head, gint *percent);
+
+void rtp_jitter_buffer_disable_buffering (RTPJitterBuffer *jbuf, gboolean disabled);
+
+RTPJitterBufferItem * rtp_jitter_buffer_peek (RTPJitterBuffer *jbuf);
+RTPJitterBufferItem * rtp_jitter_buffer_pop (RTPJitterBuffer *jbuf, gint *percent);
+
+void rtp_jitter_buffer_flush (RTPJitterBuffer *jbuf,
+ GFunc free_func, gpointer user_data);
+
+gboolean rtp_jitter_buffer_is_buffering (RTPJitterBuffer * jbuf);
+void rtp_jitter_buffer_set_buffering (RTPJitterBuffer * jbuf, gboolean buffering);
+gint rtp_jitter_buffer_get_percent (RTPJitterBuffer * jbuf);
+
+guint rtp_jitter_buffer_num_packets (RTPJitterBuffer *jbuf);
+guint32 rtp_jitter_buffer_get_ts_diff (RTPJitterBuffer *jbuf);
+
+void rtp_jitter_buffer_get_sync (RTPJitterBuffer *jbuf, guint64 *rtptime,
+ guint64 *timestamp, guint32 *clock_rate,
+ guint64 *last_rtptime);
+
+GstClockTime rtp_jitter_buffer_calculate_pts (RTPJitterBuffer * jbuf, GstClockTime dts, gboolean estimated_dts,
+ guint32 rtptime, GstClockTime base_time, gint gap,
+ gboolean is_rtx);
+
+gboolean rtp_jitter_buffer_can_fast_start (RTPJitterBuffer * jbuf, gint num_packet);
+
+gboolean rtp_jitter_buffer_is_full (RTPJitterBuffer * jbuf);
+void rtp_jitter_buffer_find_earliest (RTPJitterBuffer * jbuf, GstClockTime *pts, guint * seqnum);
+
+#endif /* __RTP_JITTER_BUFFER_H__ */
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.c b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.c
new file mode 100644
index 000000000..3cad086c4
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.c
@@ -0,0 +1,430 @@
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) 2015 Kurento (http://kurento.org/)
+ * @author: Miguel París <mparisdiaz@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "rtpstats.h"
+
+void
+gst_rtp_packet_rate_ctx_reset (RTPPacketRateCtx * ctx, gint32 clock_rate)
+{
+ ctx->clock_rate = clock_rate;
+ ctx->probed = FALSE;
+ ctx->avg_packet_rate = -1;
+ ctx->last_ts = -1;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx * ctx, guint16 seqnum,
+ guint32 ts)
+{
+ guint64 new_ts, diff_ts;
+ gint diff_seqnum;
+ gint32 new_packet_rate;
+
+ if (ctx->clock_rate <= 0) {
+ return ctx->avg_packet_rate;
+ }
+
+ new_ts = ctx->last_ts;
+ gst_rtp_buffer_ext_timestamp (&new_ts, ts);
+
+ if (!ctx->probed) {
+ ctx->probed = TRUE;
+ goto done;
+ }
+
+ diff_seqnum = gst_rtp_buffer_compare_seqnum (ctx->last_seqnum, seqnum);
+ if (diff_seqnum <= 0 || new_ts <= ctx->last_ts || diff_seqnum > 1) {
+ goto done;
+ }
+
+ diff_ts = new_ts - ctx->last_ts;
+ diff_ts = gst_util_uint64_scale_int (diff_ts, GST_SECOND, ctx->clock_rate);
+ new_packet_rate = gst_util_uint64_scale (diff_seqnum, GST_SECOND, diff_ts);
+
+ /* The goal is that higher packet rates "win".
+ * If there's a sudden burst, the average will go up fast,
+ * but it will go down again slowly.
+ * This is useful for bursty cases, where a lot of packets are close
+ * to each other and should allow a higher reorder/dropout there.
+ * Round up the new average.
+ */
+ if ((gint32) ctx->avg_packet_rate > new_packet_rate) {
+ ctx->avg_packet_rate = (7 * ctx->avg_packet_rate + new_packet_rate + 7) / 8;
+ } else {
+ ctx->avg_packet_rate = (ctx->avg_packet_rate + new_packet_rate + 1) / 2;
+ }
+
+done:
+ ctx->last_seqnum = seqnum;
+ ctx->last_ts = new_ts;
+
+ return ctx->avg_packet_rate;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get (RTPPacketRateCtx * ctx)
+{
+ return ctx->avg_packet_rate;
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get_max_dropout (RTPPacketRateCtx * ctx, gint32 time_ms)
+{
+ if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == G_MAXUINT32) {
+ return RTP_DEF_DROPOUT;
+ }
+
+ return MAX (RTP_MIN_DROPOUT, ctx->avg_packet_rate * time_ms / 1000);
+}
+
+guint32
+gst_rtp_packet_rate_ctx_get_max_misorder (RTPPacketRateCtx * ctx,
+ gint32 time_ms)
+{
+ if (time_ms <= 0 || !ctx->probed || ctx->avg_packet_rate == G_MAXUINT32) {
+ return RTP_DEF_MISORDER;
+ }
+
+ return MAX (RTP_MIN_MISORDER, ctx->avg_packet_rate * time_ms / 1000);
+}
+
+/**
+ * rtp_stats_init_defaults:
+ * @stats: an #RTPSessionStats struct
+ *
+ * Initialize @stats with its default values.
+ */
+void
+rtp_stats_init_defaults (RTPSessionStats * stats)
+{
+ rtp_stats_set_bandwidths (stats, -1, -1, -1, -1);
+ stats->min_interval = RTP_STATS_MIN_INTERVAL;
+ stats->bye_timeout = RTP_STATS_BYE_TIMEOUT;
+ stats->nacks_dropped = 0;
+ stats->nacks_sent = 0;
+ stats->nacks_received = 0;
+}
+
+/**
+ * rtp_stats_set_bandwidths:
+ * @stats: an #RTPSessionStats struct
+ * @rtp_bw: RTP bandwidth
+ * @rtcp_bw: RTCP bandwidth
+ * @rs: sender RTCP bandwidth
+ * @rr: receiver RTCP bandwidth
+ *
+ * Configure the bandwidth parameters in the stats. When an input variable is
+ * set to -1, it will be calculated from the other input variables and from the
+ * defaults.
+ */
+void
+rtp_stats_set_bandwidths (RTPSessionStats * stats, guint rtp_bw,
+ gdouble rtcp_bw, guint rs, guint rr)
+{
+ GST_DEBUG ("recalc bandwidths: RTP %u, RTCP %f, RS %u, RR %u", rtp_bw,
+ rtcp_bw, rs, rr);
+
+ /* when given, sender and receive bandwidth add up to the total
+ * rtcp bandwidth */
+ if (rs != G_MAXUINT && rr != G_MAXUINT)
+ rtcp_bw = rs + rr;
+
+ /* If rtcp_bw is between 0 and 1, it is a fraction of rtp_bw */
+ if (rtcp_bw > 0.0 && rtcp_bw < 1.0) {
+ if (rtp_bw > 0.0)
+ rtcp_bw = rtp_bw * rtcp_bw;
+ else
+ rtcp_bw = -1.0;
+ }
+
+ /* RTCP is 5% of the RTP bandwidth */
+ if (rtp_bw == G_MAXUINT && rtcp_bw > 1.0)
+ rtp_bw = rtcp_bw * 20;
+ else if (rtp_bw != G_MAXUINT && rtcp_bw < 0.0)
+ rtcp_bw = rtp_bw / 20;
+ else if (rtp_bw == G_MAXUINT && rtcp_bw < 0.0) {
+ /* nothing given, take defaults */
+ rtp_bw = RTP_STATS_BANDWIDTH;
+ rtcp_bw = rtp_bw * RTP_STATS_RTCP_FRACTION;
+ }
+
+ stats->bandwidth = rtp_bw;
+ stats->rtcp_bandwidth = rtcp_bw;
+
+ /* now figure out the fractions */
+ if (rs == G_MAXUINT) {
+ /* rs unknown */
+ if (rr == G_MAXUINT) {
+ /* both not given, use defaults */
+ rs = stats->rtcp_bandwidth * RTP_STATS_SENDER_FRACTION;
+ rr = stats->rtcp_bandwidth * RTP_STATS_RECEIVER_FRACTION;
+ } else {
+ /* rr known, calculate rs */
+ if (stats->rtcp_bandwidth > rr)
+ rs = stats->rtcp_bandwidth - rr;
+ else
+ rs = 0;
+ }
+ } else if (rr == G_MAXUINT) {
+ /* rs known, calculate rr */
+ if (stats->rtcp_bandwidth > rs)
+ rr = stats->rtcp_bandwidth - rs;
+ else
+ rr = 0;
+ }
+
+ if (stats->rtcp_bandwidth > 0) {
+ stats->sender_fraction = ((gdouble) rs) / ((gdouble) stats->rtcp_bandwidth);
+ stats->receiver_fraction = 1.0 - stats->sender_fraction;
+ } else {
+ /* no RTCP bandwidth, set dummy values */
+ stats->sender_fraction = 0.0;
+ stats->receiver_fraction = 0.0;
+ }
+ GST_DEBUG ("bandwidths: RTP %u, RTCP %u, RS %f, RR %f", stats->bandwidth,
+ stats->rtcp_bandwidth, stats->sender_fraction, stats->receiver_fraction);
+}
+
+/**
+ * rtp_stats_calculate_rtcp_interval:
+ * @stats: an #RTPSessionStats struct
+ * @sender: if we are a sender
+ * @profile: RTP profile of this session
+ * @ptp: if this session is a point-to-point session
+ * @first: if this is the first time
+ *
+ * Calculate the RTCP interval. The result of this function is the amount of
+ * time to wait (in nanoseconds) before sending a new RTCP message.
+ *
+ * Returns: the RTCP interval.
+ */
+GstClockTime
+rtp_stats_calculate_rtcp_interval (RTPSessionStats * stats, gboolean we_send,
+ GstRTPProfile profile, gboolean ptp, gboolean first)
+{
+ gdouble members, senders, n;
+ gdouble avg_rtcp_size, rtcp_bw;
+ gdouble interval;
+ gdouble rtcp_min_time;
+
+ if (profile == GST_RTP_PROFILE_AVPF || profile == GST_RTP_PROFILE_SAVPF) {
+ /* RFC 4585 3.4d), 3.5.1 */
+
+ if (first && !ptp)
+ rtcp_min_time = 1.0;
+ else
+ rtcp_min_time = 0.0;
+ } else {
+ /* Very first call at application start-up uses half the min
+ * delay for quicker notification while still allowing some time
+ * before reporting for randomization and to learn about other
+ * sources so the report interval will converge to the correct
+ * interval more quickly.
+ */
+ rtcp_min_time = stats->min_interval;
+ if (first)
+ rtcp_min_time /= 2.0;
+ }
+
+ /* Dedicate a fraction of the RTCP bandwidth to senders unless
+ * the number of senders is large enough that their share is
+ * more than that fraction.
+ */
+ n = members = stats->active_sources;
+ senders = (gdouble) stats->sender_sources;
+ rtcp_bw = stats->rtcp_bandwidth;
+
+ if (senders <= members * stats->sender_fraction) {
+ if (we_send) {
+ rtcp_bw *= stats->sender_fraction;
+ n = senders;
+ } else {
+ rtcp_bw *= stats->receiver_fraction;
+ n -= senders;
+ }
+ }
+
+ /* no bandwidth for RTCP, return NONE to signal that we don't want to send
+ * RTCP packets */
+ if (rtcp_bw <= 0.0001)
+ return GST_CLOCK_TIME_NONE;
+
+ avg_rtcp_size = 8.0 * stats->avg_rtcp_packet_size;
+ /*
+ * The effective number of sites times the average packet size is
+ * the total number of octets sent when each site sends a report.
+ * Dividing this by the effective bandwidth gives the time
+ * interval over which those packets must be sent in order to
+ * meet the bandwidth target, with a minimum enforced. In that
+ * time interval we send one report so this time is also our
+ * average time between reports.
+ */
+ GST_DEBUG ("avg size %f, n %f, rtcp_bw %f", avg_rtcp_size, n, rtcp_bw);
+ interval = avg_rtcp_size * n / rtcp_bw;
+ if (interval < rtcp_min_time)
+ interval = rtcp_min_time;
+
+ return interval * GST_SECOND;
+}
+
+/**
+ * rtp_stats_add_rtcp_jitter:
+ * @stats: an #RTPSessionStats struct
+ * @interval: an RTCP interval
+ *
+ * Apply a random jitter to the @interval. @interval is typically obtained with
+ * rtp_stats_calculate_rtcp_interval().
+ *
+ * Returns: the new RTCP interval.
+ */
+GstClockTime
+rtp_stats_add_rtcp_jitter (RTPSessionStats * stats G_GNUC_UNUSED,
+ GstClockTime interval)
+{
+ gdouble temp;
+
+ /* see RFC 3550 p 30
+ * To compensate for "unconditional reconsideration" converging to a
+ * value below the intended average.
+ */
+#define COMPENSATION (2.71828 - 1.5);
+
+ temp = (interval * g_random_double_range (0.5, 1.5)) / COMPENSATION;
+
+ return (GstClockTime) temp;
+}
+
+
+/**
+ * rtp_stats_calculate_bye_interval:
+ * @stats: an #RTPSessionStats struct
+ *
+ * Calculate the BYE interval. The result of this function is the amount of
+ * time to wait (in nanoseconds) before sending a BYE message.
+ *
+ * Returns: the BYE interval.
+ */
+GstClockTime
+rtp_stats_calculate_bye_interval (RTPSessionStats * stats)
+{
+ gdouble members;
+ gdouble avg_rtcp_size, rtcp_bw;
+ gdouble interval;
+ gdouble rtcp_min_time;
+
+ /* no interval when we have less than 50 members */
+ if (stats->active_sources < 50)
+ return 0;
+
+ rtcp_min_time = (stats->min_interval) / 2.0;
+
+ /* Dedicate a fraction of the RTCP bandwidth to senders unless
+ * the number of senders is large enough that their share is
+ * more than that fraction.
+ */
+ members = stats->bye_members;
+ rtcp_bw = stats->rtcp_bandwidth * stats->receiver_fraction;
+
+ /* no bandwidth for RTCP, return NONE to signal that we don't want to send
+ * RTCP packets */
+ if (rtcp_bw <= 0.0001)
+ return GST_CLOCK_TIME_NONE;
+
+ avg_rtcp_size = 8.0 * stats->avg_rtcp_packet_size;
+ /*
+ * The effective number of sites times the average packet size is
+ * the total number of octets sent when each site sends a report.
+ * Dividing this by the effective bandwidth gives the time
+ * interval over which those packets must be sent in order to
+ * meet the bandwidth target, with a minimum enforced. In that
+ * time interval we send one report so this time is also our
+ * average time between reports.
+ */
+ interval = avg_rtcp_size * members / rtcp_bw;
+ if (interval < rtcp_min_time)
+ interval = rtcp_min_time;
+
+ return interval * GST_SECOND;
+}
+
+/**
+ * rtp_stats_get_packets_lost:
+ * @stats: an #RTPSourceStats struct
+ *
+ * Calculate the total number of RTP packets lost since beginning of
+ * reception. Packets that arrive late are not considered lost, and
+ * duplicates are not taken into account. Hence, the loss may be negative
+ * if there are duplicates.
+ *
+ * Returns: total RTP packets lost.
+ */
+gint64
+rtp_stats_get_packets_lost (const RTPSourceStats * stats)
+{
+ gint64 lost;
+ guint64 extended_max, expected;
+
+ extended_max = stats->cycles + stats->max_seq;
+ expected = extended_max - stats->base_seq + 1;
+ lost = expected - stats->packets_received;
+
+ return lost;
+}
+
+void
+rtp_stats_set_min_interval (RTPSessionStats * stats, gdouble min_interval)
+{
+ stats->min_interval = min_interval;
+}
+
+gboolean
+__g_socket_address_equal (GSocketAddress * a, GSocketAddress * b)
+{
+ GInetSocketAddress *ia, *ib;
+ GInetAddress *iaa, *iab;
+
+ ia = G_INET_SOCKET_ADDRESS (a);
+ ib = G_INET_SOCKET_ADDRESS (b);
+
+ if (g_inet_socket_address_get_port (ia) !=
+ g_inet_socket_address_get_port (ib))
+ return FALSE;
+
+ iaa = g_inet_socket_address_get_address (ia);
+ iab = g_inet_socket_address_get_address (ib);
+
+ return g_inet_address_equal (iaa, iab);
+}
+
+gchar *
+__g_socket_address_to_string (GSocketAddress * addr)
+{
+ GInetSocketAddress *ia;
+ gchar *ret, *tmp;
+
+ ia = G_INET_SOCKET_ADDRESS (addr);
+
+ tmp = g_inet_address_to_string (g_inet_socket_address_get_address (ia));
+ ret = g_strdup_printf ("%s:%u", tmp, g_inet_socket_address_get_port (ia));
+ g_free (tmp);
+
+ return ret;
+}
diff --git a/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.h b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.h
new file mode 100644
index 000000000..bd3a54efe
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/jitterbuffer/rtpstats.h
@@ -0,0 +1,267 @@
+/* GStreamer
+ * Copyright (C) <2007> Wim Taymans <wim.taymans@gmail.com>
+ * Copyright (C) 2015 Kurento (http://kurento.org/)
+ * @author: Miguel París <mparisdiaz@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __RTP_STATS_H__
+#define __RTP_STATS_H__
+
+#include <gst/gst.h>
+#include <gst/net/gstnetaddressmeta.h>
+#include <gst/rtp/rtp.h>
+#include <gio/gio.h>
+
+/* UDP/IP is assumed for bandwidth calculation */
+#define UDP_IP_HEADER_OVERHEAD 28
+
+/**
+ * RTPSenderReport:
+ *
+ * A sender report structure.
+ */
+typedef struct {
+ gboolean is_valid;
+ guint64 ntptime;
+ guint32 rtptime;
+ guint32 packet_count;
+ guint32 octet_count;
+ GstClockTime time;
+} RTPSenderReport;
+
+/**
+ * RTPReceiverReport:
+ *
+ * A receiver report structure.
+ */
+typedef struct {
+ gboolean is_valid;
+ guint32 ssrc; /* who the report is from */
+ guint8 fractionlost;
+ guint32 packetslost;
+ guint32 exthighestseq;
+ guint32 jitter;
+ guint32 lsr;
+ guint32 dlsr;
+ guint32 round_trip;
+} RTPReceiverReport;
+
+/**
+ * RTPPacketInfo:
+ * @send: if this is a packet for sending
+ * @rtp: if this info is about an RTP packet
+ * @is_list: if this is a bufferlist
+ * @data: a #GstBuffer or #GstBufferList
+ * @address: address of the sender of the packet
+ * @current_time: current time according to the system clock
+ * @running_time: time of a packet as buffer running_time
+ * @ntpnstime: time of a packet NTP time in nanoseconds
+ * @header_len: number of overhead bytes per packet
+ * @bytes: bytes of the packet including lowlevel overhead
+ * @payload_len: bytes of the RTP payload
+ * @seqnum: the seqnum of the packet
+ * @pt: the payload type of the packet
+ * @rtptime: the RTP time of the packet
+ *
+ * Structure holding information about the packet.
+ */
+typedef struct {
+ gboolean send;
+ gboolean rtp;
+ gboolean is_list;
+ gpointer data;
+ GSocketAddress *address;
+ GstClockTime current_time;
+ GstClockTime running_time;
+ guint64 ntpnstime;
+ guint header_len;
+ guint bytes;
+ guint packets;
+ guint payload_len;
+ guint32 ssrc;
+ guint16 seqnum;
+ guint8 pt;
+ guint32 rtptime;
+ guint32 csrc_count;
+ guint32 csrcs[16];
+} RTPPacketInfo;
+
+/**
+ * RTPSourceStats:
+ * @packets_received: number of received packets in total
+ * @prev_received: number of packets received in previous reporting
+ * interval
+ * @octets_received: number of payload bytes received
+ * @bytes_received: number of total bytes received including headers and lower
+ * protocol level overhead
+ * @max_seqnr: highest sequence number received
+ * @transit: previous transit time used for calculating @jitter
+ * @jitter: current jitter (in clock rate units scaled by 16 for precision)
+ * @prev_rtptime: previous time when an RTP packet was received
+ * @prev_rtcptime: previous time when an RTCP packet was received
+ * @last_rtptime: time when last RTP packet received
+ * @last_rtcptime: time when last RTCP packet received
+ * @curr_rr: index of current @rr block
+ * @rr: previous and current receiver report block
+ * @curr_sr: index of current @sr block
+ * @sr: previous and current sender report block
+ *
+ * Stats about a source.
+ */
+typedef struct {
+ guint64 packets_received;
+ guint64 octets_received;
+ guint64 bytes_received;
+
+ guint32 prev_expected;
+ guint32 prev_received;
+
+ guint16 max_seq;
+ guint64 cycles;
+ guint32 base_seq;
+ guint32 bad_seq;
+ guint32 transit;
+ guint32 jitter;
+
+ guint64 packets_sent;
+ guint64 octets_sent;
+
+ guint sent_pli_count;
+ guint recv_pli_count;
+ guint sent_fir_count;
+ guint recv_fir_count;
+ guint sent_nack_count;
+ guint recv_nack_count;
+
+ /* when we received stuff */
+ GstClockTime prev_rtptime;
+ GstClockTime prev_rtcptime;
+ GstClockTime last_rtptime;
+ GstClockTime last_rtcptime;
+
+ /* sender and receiver reports */
+ gint curr_rr;
+ RTPReceiverReport rr[2];
+ gint curr_sr;
+ RTPSenderReport sr[2];
+} RTPSourceStats;
+
+#define RTP_STATS_BANDWIDTH 64000
+#define RTP_STATS_RTCP_FRACTION 0.05
+/*
+ * Minimum average time between RTCP packets from this site (in
+ * seconds). This time prevents the reports from `clumping' when
+ * sessions are small and the law of large numbers isn't helping
+ * to smooth out the traffic. It also keeps the report interval
+ * from becoming ridiculously small during transient outages like
+ * a network partition.
+ */
+#define RTP_STATS_MIN_INTERVAL 5.0
+/*
+ * Fraction of the RTCP bandwidth to be shared among active
+ * senders. (This fraction was chosen so that in a typical
+ * session with one or two active senders, the computed report
+ * time would be roughly equal to the minimum report time so that
+ * we don't unnecessarily slow down receiver reports.) The
+ * receiver fraction must be 1 - the sender fraction.
+ */
+#define RTP_STATS_SENDER_FRACTION (0.25)
+#define RTP_STATS_RECEIVER_FRACTION (1.0 - RTP_STATS_SENDER_FRACTION)
+
+/*
+ * When receiving a BYE from a source, remove the source from the database
+ * after this timeout.
+ */
+#define RTP_STATS_BYE_TIMEOUT (2 * GST_SECOND)
+
+/*
+ * The default and minimum values of the maximum number of missing packets we tolerate.
+ * These are packets with asequence number bigger than the last seen packet.
+ */
+#define RTP_DEF_DROPOUT 3000
+#define RTP_MIN_DROPOUT 30
+
+/*
+ * The default and minimum values of the maximum number of misordered packets we tolerate.
+ * These are packets with a sequence number smaller than the last seen packet.
+ */
+#define RTP_DEF_MISORDER 100
+#define RTP_MIN_MISORDER 10
+
+/**
+ * RTPPacketRateCtx:
+ *
+ * Context to calculate the pseudo-average packet rate.
+ */
+typedef struct {
+ gboolean probed;
+ gint32 clock_rate;
+ guint16 last_seqnum;
+ guint64 last_ts;
+ guint32 avg_packet_rate;
+} RTPPacketRateCtx;
+
+void gst_rtp_packet_rate_ctx_reset (RTPPacketRateCtx * ctx, gint32 clock_rate);
+guint32 gst_rtp_packet_rate_ctx_update (RTPPacketRateCtx *ctx, guint16 seqnum, guint32 ts);
+guint32 gst_rtp_packet_rate_ctx_get (RTPPacketRateCtx *ctx);
+guint32 gst_rtp_packet_rate_ctx_get_max_dropout (RTPPacketRateCtx *ctx, gint32 time_ms);
+guint32 gst_rtp_packet_rate_ctx_get_max_misorder (RTPPacketRateCtx *ctx, gint32 time_ms);
+
+/**
+ * RTPSessionStats:
+ *
+ * Stats kept for a session and used to produce RTCP packet timeouts.
+ */
+typedef struct {
+ guint bandwidth;
+ guint rtcp_bandwidth;
+ gdouble sender_fraction;
+ gdouble receiver_fraction;
+ gdouble min_interval;
+ GstClockTime bye_timeout;
+ guint internal_sources;
+ guint sender_sources;
+ guint internal_sender_sources;
+ guint active_sources;
+ guint avg_rtcp_packet_size;
+ guint bye_members;
+ guint nacks_dropped;
+ guint nacks_sent;
+ guint nacks_received;
+} RTPSessionStats;
+
+void rtp_stats_init_defaults (RTPSessionStats *stats);
+
+void rtp_stats_set_bandwidths (RTPSessionStats *stats,
+ guint rtp_bw,
+ gdouble rtcp_bw,
+ guint rs, guint rr);
+
+GstClockTime rtp_stats_calculate_rtcp_interval (RTPSessionStats *stats, gboolean sender, GstRTPProfile profile, gboolean ptp, gboolean first);
+GstClockTime rtp_stats_add_rtcp_jitter (RTPSessionStats *stats, GstClockTime interval);
+GstClockTime rtp_stats_calculate_bye_interval (RTPSessionStats *stats);
+gint64 rtp_stats_get_packets_lost (const RTPSourceStats *stats);
+
+void rtp_stats_set_min_interval (RTPSessionStats *stats,
+ gdouble min_interval);
+
+
+gboolean __g_socket_address_equal (GSocketAddress *a, GSocketAddress *b);
+gchar * __g_socket_address_to_string (GSocketAddress * addr);
+
+#endif /* __RTP_STATS_H__ */
diff --git a/generic/gst-plugin-threadshare/src/lib.rs b/generic/gst-plugin-threadshare/src/lib.rs
new file mode 100644
index 000000000..42ecc114d
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/lib.rs
@@ -0,0 +1,117 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! A collection of GStreamer plugins which leverage the `threadshare` [`runtime`].
+//!
+//! [`runtime`]: runtime/index.html
+
+// Needed for `select!` in `Socket::next`
+// see https://docs.rs/futures/0.3.1/futures/macro.select.html
+#![recursion_limit = "1024"]
+#![crate_type = "cdylib"]
+
+pub use tokio;
+
+#[macro_use]
+pub mod runtime;
+
+pub mod socket;
+mod tcpclientsrc;
+mod udpsink;
+mod udpsrc;
+
+mod appsrc;
+pub mod dataqueue;
+mod inputselector;
+mod jitterbuffer;
+mod proxy;
+mod queue;
+
+use glib::translate::*;
+use glib_sys as glib_ffi;
+
+use gst;
+use gst::gst_plugin_define;
+use gst::prelude::*;
+use gstreamer_sys as gst_ffi;
+
+fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ udpsrc::register(plugin)?;
+ udpsink::register(plugin)?;
+ tcpclientsrc::register(plugin)?;
+ queue::register(plugin)?;
+ proxy::register(plugin)?;
+ appsrc::register(plugin)?;
+ jitterbuffer::jitterbuffer::register(plugin)?;
+ inputselector::register(plugin)?;
+
+ Ok(())
+}
+
+gst_plugin_define!(
+ threadshare,
+ env!("CARGO_PKG_DESCRIPTION"),
+ plugin_init,
+ concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
+ "LGPL",
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_REPOSITORY"),
+ env!("BUILD_REL_DATE")
+);
+
+pub fn set_element_flags<T: glib::IsA<gst::Object> + glib::IsA<gst::Element>>(
+ element: &T,
+ flags: gst::ElementFlags,
+) {
+ unsafe {
+ let ptr: *mut gst_ffi::GstObject = element.as_ptr() as *mut _;
+ let _guard = MutexGuard::lock(&(*ptr).lock);
+ (*ptr).flags |= flags.to_glib();
+ }
+}
+
+struct MutexGuard<'a>(&'a glib_ffi::GMutex);
+
+impl<'a> MutexGuard<'a> {
+ pub fn lock(mutex: &'a glib_ffi::GMutex) -> Self {
+ unsafe {
+ glib_ffi::g_mutex_lock(mut_override(mutex));
+ }
+ MutexGuard(mutex)
+ }
+}
+
+impl<'a> Drop for MutexGuard<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ glib_ffi::g_mutex_unlock(mut_override(self.0));
+ }
+ }
+}
+
+pub fn get_current_running_time(element: &gst::Element) -> gst::ClockTime {
+ if let Some(clock) = element.get_clock() {
+ if clock.get_time() > element.get_base_time() {
+ clock.get_time() - element.get_base_time()
+ } else {
+ 0.into()
+ }
+ } else {
+ gst::CLOCK_TIME_NONE
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/proxy.rs b/generic/gst-plugin-threadshare/src/proxy.rs
new file mode 100644
index 000000000..19145451a
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/proxy.rs
@@ -0,0 +1,1332 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::channel::oneshot;
+use futures::future::BoxFuture;
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_log, gst_trace};
+
+use lazy_static::lazy_static;
+
+use std::collections::{HashMap, VecDeque};
+use std::sync::Mutex as StdMutex;
+use std::sync::MutexGuard as StdMutexGuard;
+use std::sync::{Arc, Weak};
+use std::{u32, u64};
+
+use crate::runtime::prelude::*;
+use crate::runtime::{
+ Context, PadSink, PadSinkRef, PadSinkWeak, PadSrc, PadSrcRef, PadSrcWeak, Task,
+};
+
+use super::dataqueue::{DataQueue, DataQueueItem, DataQueueState};
+
+lazy_static! {
+ static ref PROXY_CONTEXTS: StdMutex<HashMap<String, Weak<StdMutex<ProxyContextInner>>>> =
+ StdMutex::new(HashMap::new());
+ static ref PROXY_SRC_PADS: StdMutex<HashMap<String, PadSrcWeak>> =
+ StdMutex::new(HashMap::new());
+ static ref PROXY_SINK_PADS: StdMutex<HashMap<String, PadSinkWeak>> =
+ StdMutex::new(HashMap::new());
+}
+
+const DEFAULT_PROXY_CONTEXT: &str = "";
+
+const DEFAULT_MAX_SIZE_BUFFERS: u32 = 200;
+const DEFAULT_MAX_SIZE_BYTES: u32 = 1024 * 1024;
+const DEFAULT_MAX_SIZE_TIME: u64 = gst::SECOND_VAL;
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct SettingsSink {
+ proxy_context: String,
+}
+
+impl Default for SettingsSink {
+ fn default() -> Self {
+ SettingsSink {
+ proxy_context: DEFAULT_PROXY_CONTEXT.into(),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct SettingsSrc {
+ max_size_buffers: u32,
+ max_size_bytes: u32,
+ max_size_time: u64,
+ context: String,
+ context_wait: u32,
+ proxy_context: String,
+}
+
+impl Default for SettingsSrc {
+ fn default() -> Self {
+ SettingsSrc {
+ max_size_buffers: DEFAULT_MAX_SIZE_BUFFERS,
+ max_size_bytes: DEFAULT_MAX_SIZE_BYTES,
+ max_size_time: DEFAULT_MAX_SIZE_TIME,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ proxy_context: DEFAULT_PROXY_CONTEXT.into(),
+ }
+ }
+}
+
+static PROPERTIES_SRC: [subclass::Property; 6] = [
+ subclass::Property("max-size-buffers", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max Size Buffers",
+ "Maximum number of buffers to queue (0=unlimited)",
+ 0,
+ u32::MAX,
+ DEFAULT_MAX_SIZE_BUFFERS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-size-bytes", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max Size Bytes",
+ "Maximum number of bytes to queue (0=unlimited)",
+ 0,
+ u32::MAX,
+ DEFAULT_MAX_SIZE_BYTES,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-size-time", |name| {
+ glib::ParamSpec::uint64(
+ name,
+ "Max Size Time",
+ "Maximum number of nanoseconds to queue (0=unlimited)",
+ 0,
+ u64::MAX - 1,
+ DEFAULT_MAX_SIZE_TIME,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("proxy-context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Proxy Context",
+ "Context name of the proxy to share with",
+ Some(DEFAULT_PROXY_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+static PROPERTIES_SINK: [subclass::Property; 1] = [subclass::Property("proxy-context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Proxy Context",
+ "Context name of the proxy to share with",
+ Some(DEFAULT_PROXY_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+})];
+
+// TODO: Refactor into a Sender and Receiver instead of the have_ booleans
+
+#[derive(Debug, Default)]
+struct PendingQueue {
+ more_queue_space_sender: Option<oneshot::Sender<()>>,
+ scheduled: bool,
+ items: VecDeque<DataQueueItem>,
+}
+
+impl PendingQueue {
+ fn notify_more_queue_space(&mut self) {
+ self.more_queue_space_sender.take();
+ }
+}
+
+#[derive(Debug)]
+struct ProxyContextInner {
+ name: String,
+ dataqueue: Option<DataQueue>,
+ last_res: Result<gst::FlowSuccess, gst::FlowError>,
+ pending_queue: Option<PendingQueue>,
+ have_sink: bool,
+ have_src: bool,
+}
+
+impl Drop for ProxyContextInner {
+ fn drop(&mut self) {
+ let mut proxy_ctxs = PROXY_CONTEXTS.lock().unwrap();
+ proxy_ctxs.remove(&self.name);
+ }
+}
+
+#[derive(Debug)]
+struct ProxyContext {
+ shared: Arc<StdMutex<ProxyContextInner>>,
+ as_sink: bool,
+ name: String,
+}
+
+impl ProxyContext {
+ #[inline]
+ fn lock_shared(&self) -> StdMutexGuard<'_, ProxyContextInner> {
+ self.shared.lock().unwrap()
+ }
+
+ fn get(name: &str, as_sink: bool) -> Option<Self> {
+ let mut proxy_ctxs = PROXY_CONTEXTS.lock().unwrap();
+
+ let mut proxy_ctx = None;
+ if let Some(shared_weak) = proxy_ctxs.get(name) {
+ if let Some(shared) = shared_weak.upgrade() {
+ {
+ let shared = shared.lock().unwrap();
+ if (shared.have_sink && as_sink) || (shared.have_src && !as_sink) {
+ return None;
+ }
+ }
+
+ proxy_ctx = Some({
+ let proxy_ctx = ProxyContext {
+ shared,
+ as_sink,
+ name: name.into(),
+ };
+ {
+ let mut shared = proxy_ctx.lock_shared();
+ if as_sink {
+ shared.have_sink = true;
+ } else {
+ shared.have_src = true;
+ }
+ }
+
+ proxy_ctx
+ });
+ }
+ }
+
+ if proxy_ctx.is_none() {
+ let shared = Arc::new(StdMutex::new(ProxyContextInner {
+ name: name.into(),
+ dataqueue: None,
+ last_res: Err(gst::FlowError::Flushing),
+ pending_queue: None,
+ have_sink: as_sink,
+ have_src: !as_sink,
+ }));
+
+ proxy_ctxs.insert(name.into(), Arc::downgrade(&shared));
+
+ proxy_ctx = Some(ProxyContext {
+ shared,
+ as_sink,
+ name: name.into(),
+ });
+ }
+
+ proxy_ctx
+ }
+}
+
+impl Drop for ProxyContext {
+ fn drop(&mut self) {
+ let mut shared_ctx = self.lock_shared();
+ if self.as_sink {
+ assert!(shared_ctx.have_sink);
+ shared_ctx.have_sink = false;
+ let _ = shared_ctx.pending_queue.take();
+ } else {
+ assert!(shared_ctx.have_src);
+ shared_ctx.have_src = false;
+ let _ = shared_ctx.dataqueue.take();
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct ProxySinkPadHandler;
+
+impl PadSinkHandler for ProxySinkPadHandler {
+ type ElementImpl = ProxySink;
+
+ fn sink_chain(
+ &self,
+ pad: &PadSinkRef,
+ _proxysink: &ProxySink,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Handling {:?}", buffer);
+ let proxysink = ProxySink::from_instance(&element);
+ proxysink
+ .enqueue_item(&element, DataQueueItem::Buffer(buffer))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ pad: &PadSinkRef,
+ _proxysink: &ProxySink,
+ element: &gst::Element,
+ list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Handling {:?}", list);
+ let proxysink = ProxySink::from_instance(&element);
+ proxysink
+ .enqueue_item(&element, DataQueueItem::BufferList(list))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ pad: &PadSinkRef,
+ proxysink: &ProxySink,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_debug!(SINK_CAT, obj: pad.gst_pad(), "Handling non-serialized {:?}", event);
+
+ let src_pad = {
+ let proxy_ctx = proxysink.proxy_ctx.lock().unwrap();
+
+ PROXY_SRC_PADS
+ .lock()
+ .unwrap()
+ .get(&proxy_ctx.as_ref().unwrap().name)
+ .and_then(|src_pad| src_pad.upgrade())
+ .map(|src_pad| src_pad.gst_pad().clone())
+ };
+
+ if let EventView::FlushStart(..) = event.view() {
+ proxysink.stop(&element).unwrap();
+ }
+
+ if let Some(src_pad) = src_pad {
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Forwarding non-serialized {:?}", event);
+ src_pad.push_event(event)
+ } else {
+ gst_error!(SINK_CAT, obj: pad.gst_pad(), "No src pad to forward non-serialized {:?} to", event);
+ true
+ }
+ }
+
+ fn sink_event_serialized(
+ &self,
+ pad: &PadSinkRef,
+ _proxysink: &ProxySink,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ use gst::EventView;
+
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Handling serialized {:?}", event);
+
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ let proxysink = ProxySink::from_instance(&element);
+
+ match event.view() {
+ EventView::Eos(..) => {
+ let _ =
+ element.post_message(&gst::Message::new_eos().src(Some(&element)).build());
+ }
+ EventView::FlushStop(..) => proxysink.start(&element).unwrap(),
+ _ => (),
+ }
+
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Queuing serialized {:?}", event);
+ proxysink
+ .enqueue_item(&element, DataQueueItem::Event(event))
+ .await
+ .is_ok()
+ }
+ .boxed()
+ }
+}
+
+#[derive(Debug)]
+struct ProxySink {
+ sink_pad: PadSink,
+ proxy_ctx: StdMutex<Option<ProxyContext>>,
+ settings: StdMutex<SettingsSink>,
+}
+
+lazy_static! {
+ static ref SINK_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-proxysink",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing proxy sink"),
+ );
+}
+
+impl ProxySink {
+ fn schedule_pending_queue(
+ &self,
+ element: &gst::Element,
+ pending_queue: &mut PendingQueue,
+ ) -> impl Future<Output = ()> {
+ gst_log!(SINK_CAT, obj: element, "Scheduling pending queue now");
+
+ pending_queue.scheduled = true;
+
+ let element = element.clone();
+ async move {
+ let sink = Self::from_instance(&element);
+
+ loop {
+ let more_queue_space_receiver = {
+ let proxy_ctx = sink.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+
+ gst_log!(SINK_CAT, obj: &element, "Trying to empty pending queue");
+
+ let ProxyContextInner {
+ pending_queue: ref mut pq,
+ ref dataqueue,
+ ..
+ } = *shared_ctx;
+
+ if let Some(ref mut pending_queue) = *pq {
+ if let Some(ref dataqueue) = dataqueue {
+ let mut failed_item = None;
+ while let Some(item) = pending_queue.items.pop_front() {
+ if let Err(item) = dataqueue.push(item) {
+ failed_item = Some(item);
+ break;
+ }
+ }
+
+ if let Some(failed_item) = failed_item {
+ pending_queue.items.push_front(failed_item);
+ let (sender, receiver) = oneshot::channel();
+ pending_queue.more_queue_space_sender = Some(sender);
+
+ receiver
+ } else {
+ gst_log!(SINK_CAT, obj: &element, "Pending queue is empty now");
+ *pq = None;
+ return;
+ }
+ } else {
+ let (sender, receiver) = oneshot::channel();
+ pending_queue.more_queue_space_sender = Some(sender);
+
+ receiver
+ }
+ } else {
+ gst_log!(SINK_CAT, obj: &element, "Flushing, dropping pending queue");
+ *pq = None;
+ return;
+ }
+ };
+
+ gst_log!(SINK_CAT, obj: &element, "Waiting for more queue space");
+ let _ = more_queue_space_receiver.await;
+ }
+ }
+ }
+
+ async fn enqueue_item(
+ &self,
+ element: &gst::Element,
+ item: DataQueueItem,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let wait_fut = {
+ let proxy_ctx = self.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+
+ /* We've taken the lock again, make sure not to recreate
+ * a pending queue if tearing down */
+ shared_ctx.last_res?;
+
+ let item = {
+ let ProxyContextInner {
+ ref mut pending_queue,
+ ref dataqueue,
+ ..
+ } = *shared_ctx;
+
+ match (pending_queue, dataqueue) {
+ (None, Some(ref dataqueue)) => dataqueue.push(item),
+ (Some(ref mut pending_queue), Some(ref dataqueue)) => {
+ if !pending_queue.scheduled {
+ let mut failed_item = None;
+ while let Some(item) = pending_queue.items.pop_front() {
+ if let Err(item) = dataqueue.push(item) {
+ failed_item = Some(item);
+ break;
+ }
+ }
+
+ if let Some(failed_item) = failed_item {
+ pending_queue.items.push_front(failed_item);
+
+ Err(item)
+ } else {
+ dataqueue.push(item)
+ }
+ } else {
+ Err(item)
+ }
+ }
+ _ => Err(item),
+ }
+ };
+
+ if let Err(item) = item {
+ if shared_ctx
+ .pending_queue
+ .as_ref()
+ .map(|pending_queue| !pending_queue.scheduled)
+ .unwrap_or(true)
+ {
+ if shared_ctx.pending_queue.is_none() {
+ shared_ctx.pending_queue = Some(PendingQueue::default());
+ }
+
+ let pending_queue = shared_ctx.pending_queue.as_mut().unwrap();
+
+ let schedule_now = match item {
+ DataQueueItem::Event(ref ev) if ev.get_type() != gst::EventType::Eos => {
+ false
+ }
+ _ => true,
+ };
+
+ pending_queue.items.push_back(item);
+
+ gst_log!(
+ SINK_CAT,
+ obj: element,
+ "Proxy is full - Pushing first item on pending queue"
+ );
+
+ if schedule_now {
+ let wait_fut = self.schedule_pending_queue(element, pending_queue);
+ Some(wait_fut)
+ } else {
+ gst_log!(SINK_CAT, obj: element, "Scheduling pending queue later");
+
+ None
+ }
+ } else {
+ shared_ctx
+ .pending_queue
+ .as_mut()
+ .unwrap()
+ .items
+ .push_back(item);
+
+ None
+ }
+ } else {
+ None
+ }
+ };
+
+ if let Some(wait_fut) = wait_fut {
+ gst_log!(
+ SINK_CAT,
+ obj: element,
+ "Blocking until queue has space again"
+ );
+ wait_fut.await;
+ }
+
+ let proxy_ctx = self.proxy_ctx.lock().unwrap();
+ let shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ shared_ctx.last_res
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(SINK_CAT, obj: element, "Preparing");
+
+ let proxy_context = self.settings.lock().unwrap().proxy_context.to_string();
+
+ let proxy_ctx = ProxyContext::get(&proxy_context, true).ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to create or get ProxyContext"]
+ )
+ })?;
+
+ {
+ let mut proxy_sink_pads = PROXY_SINK_PADS.lock().unwrap();
+ assert!(!proxy_sink_pads.contains_key(&proxy_context));
+ proxy_sink_pads.insert(proxy_context, self.sink_pad.downgrade());
+ }
+
+ *self.proxy_ctx.lock().unwrap() = Some(proxy_ctx);
+
+ self.sink_pad.prepare(&ProxySinkPadHandler);
+
+ gst_debug!(SINK_CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(SINK_CAT, obj: element, "Unpreparing");
+
+ self.sink_pad.unprepare();
+ *self.proxy_ctx.lock().unwrap() = None;
+
+ gst_debug!(SINK_CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let proxy_ctx = self.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+
+ gst_debug!(SINK_CAT, obj: element, "Starting");
+
+ {
+ let settings = self.settings.lock().unwrap();
+ let mut proxy_sink_pads = PROXY_SINK_PADS.lock().unwrap();
+ proxy_sink_pads.remove(&settings.proxy_context);
+ }
+
+ shared_ctx.last_res = Ok(gst::FlowSuccess::Ok);
+
+ gst_debug!(SINK_CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ let proxy_ctx = self.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+
+ gst_debug!(SINK_CAT, obj: element, "Stopping");
+
+ let _ = shared_ctx.pending_queue.take();
+ shared_ctx.last_res = Err(gst::FlowError::Flushing);
+
+ gst_debug!(SINK_CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for ProxySink {
+ const NAME: &'static str = "RsTsProxySink";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing proxy sink",
+ "Sink/Generic",
+ "Thread-sharing proxy sink",
+ "Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+
+ klass.install_properties(&PROPERTIES_SINK);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ sink_pad: PadSink::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("sink").unwrap(),
+ Some("sink"),
+ )),
+ proxy_ctx: StdMutex::new(None),
+ settings: StdMutex::new(SettingsSink::default()),
+ }
+ }
+}
+
+impl ObjectImpl for ProxySink {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES_SINK[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("proxy-context", ..) => {
+ settings.proxy_context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES_SINK[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("proxy-context", ..) => Ok(settings.proxy_context.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.sink_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SINK);
+ }
+}
+
+impl ElementImpl for ProxySink {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(SINK_CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let success = self.parent_change_state(element, transition)?;
+
+ if transition == gst::StateChange::ReadyToPaused {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+
+ Ok(success)
+ }
+}
+
+#[derive(Clone, Debug)]
+struct ProxySrcPadHandler;
+
+impl ProxySrcPadHandler {
+ async fn push_item(
+ pad: &PadSrcRef<'_>,
+ proxysrc: &ProxySrc,
+ item: DataQueueItem,
+ ) -> Result<(), gst::FlowError> {
+ {
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ if let Some(ref mut pending_queue) = shared_ctx.pending_queue {
+ pending_queue.notify_more_queue_space();
+ }
+ }
+
+ match item {
+ DataQueueItem::Buffer(buffer) => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Forwarding {:?}", buffer);
+ pad.push(buffer).await.map(drop)
+ }
+ DataQueueItem::BufferList(list) => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Forwarding {:?}", list);
+ pad.push_list(list).await.map(drop)
+ }
+ DataQueueItem::Event(event) => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ pad.push_event(event).await;
+ Ok(())
+ }
+ }
+ }
+}
+
+impl PadSrcHandler for ProxySrcPadHandler {
+ type ElementImpl = ProxySrc;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ proxysrc: &ProxySrc,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let sink_pad = {
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+
+ PROXY_SINK_PADS
+ .lock()
+ .unwrap()
+ .get(&proxy_ctx.as_ref().unwrap().name)
+ .and_then(|sink_pad| sink_pad.upgrade())
+ .map(|sink_pad| sink_pad.gst_pad().clone())
+ };
+
+ match event.view() {
+ EventView::FlushStart(..) => proxysrc.stop(element).unwrap(),
+ EventView::FlushStop(..) => proxysrc.flush_stop(element),
+ _ => (),
+ }
+
+ if let Some(sink_pad) = sink_pad {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ sink_pad.push_event(event)
+ } else {
+ gst_error!(SRC_CAT, obj: pad.gst_pad(), "No sink pad to forward {:?} to", event);
+ false
+ }
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ _proxysrc: &ProxySrc,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+ let ret = match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ q.set(true, 0.into(), gst::CLOCK_TIME_NONE);
+ true
+ }
+ QueryView::Scheduling(ref mut q) => {
+ q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0);
+ q.add_scheduling_modes(&[gst::PadMode::Push]);
+ true
+ }
+ QueryView::Caps(ref mut q) => {
+ let caps = if let Some(ref caps) = pad.gst_pad().get_current_caps() {
+ q.get_filter()
+ .map(|f| f.intersect_with_mode(caps, gst::CapsIntersectMode::First))
+ .unwrap_or_else(|| caps.clone())
+ } else {
+ q.get_filter()
+ .map(|f| f.to_owned())
+ .unwrap_or_else(gst::Caps::new_any)
+ };
+
+ q.set_result(&caps);
+
+ true
+ }
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Handled {:?}", query);
+ } else {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Didn't handle {:?}", query);
+ }
+
+ ret
+ }
+}
+
+#[derive(Debug)]
+struct ProxySrc {
+ src_pad: PadSrc,
+ task: Task,
+ proxy_ctx: StdMutex<Option<ProxyContext>>,
+ dataqueue: StdMutex<Option<DataQueue>>,
+ settings: StdMutex<SettingsSrc>,
+}
+
+lazy_static! {
+ static ref SRC_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-proxysrc",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing proxy source"),
+ );
+}
+
+impl ProxySrc {
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(SRC_CAT, obj: element, "Preparing");
+
+ let settings = self.settings.lock().unwrap().clone();
+
+ let proxy_ctx = ProxyContext::get(&settings.proxy_context, false).ok_or_else(|| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to create get shared_state"]
+ )
+ })?;
+
+ let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ let dataqueue = DataQueue::new(
+ &element.clone().upcast(),
+ self.src_pad.gst_pad(),
+ if settings.max_size_buffers == 0 {
+ None
+ } else {
+ Some(settings.max_size_buffers)
+ },
+ if settings.max_size_bytes == 0 {
+ None
+ } else {
+ Some(settings.max_size_bytes)
+ },
+ if settings.max_size_time == 0 {
+ None
+ } else {
+ Some(settings.max_size_time)
+ },
+ );
+
+ {
+ let mut shared_ctx = proxy_ctx.lock_shared();
+ shared_ctx.dataqueue = Some(dataqueue.clone());
+
+ let mut proxy_src_pads = PROXY_SRC_PADS.lock().unwrap();
+ assert!(!proxy_src_pads.contains_key(&settings.proxy_context));
+ proxy_src_pads.insert(settings.proxy_context, self.src_pad.downgrade());
+ }
+
+ *self.proxy_ctx.lock().unwrap() = Some(proxy_ctx);
+
+ *self.dataqueue.lock().unwrap() = Some(dataqueue);
+
+ self.src_pad.prepare(&ProxySrcPadHandler);
+
+ self.task.prepare(ts_ctx).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+
+ gst_debug!(SRC_CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(SRC_CAT, obj: element, "Unpreparing");
+
+ {
+ let settings = self.settings.lock().unwrap();
+ let mut proxy_src_pads = PROXY_SRC_PADS.lock().unwrap();
+ proxy_src_pads.remove(&settings.proxy_context);
+ }
+
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ *self.dataqueue.lock().unwrap() = None;
+ *self.proxy_ctx.lock().unwrap() = None;
+
+ gst_debug!(SRC_CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ // Keep the lock on the `dataqueue` until `stop` is complete
+ // so as to prevent race conditions due to concurrent FlushStart/Stop.
+ // Note that this won't deadlock as `ProxySrc::dataqueue` guards a pointer to
+ // the `dataqueue` used within the `src_pad`'s `Task`.
+ let dataqueue = self.dataqueue.lock().unwrap();
+ gst_debug!(SRC_CAT, obj: element, "Stopping");
+
+ self.task.stop();
+
+ let dataqueue = dataqueue.as_ref().unwrap();
+ dataqueue.clear();
+ dataqueue.stop();
+
+ gst_debug!(SRC_CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let dataqueue = self.dataqueue.lock().unwrap();
+ let dataqueue = dataqueue.as_ref().unwrap();
+ if dataqueue.state() == DataQueueState::Started {
+ gst_debug!(SRC_CAT, obj: element, "Already started");
+ return Ok(());
+ }
+
+ gst_debug!(SRC_CAT, obj: element, "Starting");
+
+ self.start_unchecked(element, dataqueue);
+
+ gst_debug!(SRC_CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_unchecked(&self, element: &gst::Element, dataqueue: &DataQueue) {
+ dataqueue.start();
+
+ {
+ let proxy_ctx = self.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ if let Some(pending_queue) = shared_ctx.pending_queue.as_mut() {
+ pending_queue.notify_more_queue_space();
+ }
+ }
+
+ self.start_task(element, dataqueue);
+ }
+
+ fn start_task(&self, element: &gst::Element, dataqueue: &DataQueue) {
+ let pad_weak = self.src_pad.downgrade();
+ let dataqueue = dataqueue.clone();
+ let element = element.clone();
+
+ self.task.start(move || {
+ let pad_weak = pad_weak.clone();
+ let mut dataqueue = dataqueue.clone();
+ let element = element.clone();
+
+ async move {
+ let item = dataqueue.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+ let item = match item {
+ Some(item) => item,
+ None => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "DataQueue Stopped or Paused");
+ return glib::Continue(false);
+ }
+ };
+
+ let proxysrc = ProxySrc::from_instance(&element);
+
+ match ProxySrcPadHandler::push_item(&pad, &proxysrc, item).await {
+ Ok(_) => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Successfully pushed item");
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ shared_ctx.last_res = Ok(gst::FlowSuccess::Ok);
+ glib::Continue(true)
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(SRC_CAT, obj: pad.gst_pad(), "Flushing");
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ shared_ctx.last_res = Err(gst::FlowError::Flushing);
+ glib::Continue(false)
+ }
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(SRC_CAT, obj: pad.gst_pad(), "EOS");
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ shared_ctx.last_res = Err(gst::FlowError::Eos);
+ glib::Continue(false)
+ }
+ Err(err) => {
+ gst_error!(SRC_CAT, obj: pad.gst_pad(), "Got error {}", err);
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap();
+ let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared();
+ shared_ctx.last_res = Err(err);
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ // Keep the lock on the `dataqueue` until `flush_stop` is complete
+ // so as to prevent race conditions due to concurrent state transitions.
+ // Note that this won't deadlock as `ProxySrc::dataqueue` guards a pointer to
+ // the `dataqueue` used within the `src_pad`'s `Task`.
+ let dataqueue = self.dataqueue.lock().unwrap();
+ let dataqueue = dataqueue.as_ref().unwrap();
+ if dataqueue.state() == DataQueueState::Started {
+ gst_debug!(SRC_CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(SRC_CAT, obj: element, "Stopping Flush");
+
+ self.task.stop();
+ self.start_unchecked(element, dataqueue);
+
+ gst_debug!(SRC_CAT, obj: element, "Stopped Flush");
+ }
+
+ fn pause(&self, element: &gst::Element) -> Result<(), ()> {
+ let dataqueue = self.dataqueue.lock().unwrap();
+ gst_debug!(SRC_CAT, obj: element, "Pausing");
+
+ dataqueue.as_ref().unwrap().pause();
+ self.task.pause();
+
+ gst_debug!(SRC_CAT, obj: element, "Paused");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for ProxySrc {
+ const NAME: &'static str = "RsTsProxySrc";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing proxy source",
+ "Source/Generic",
+ "Thread-sharing proxy source",
+ "Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES_SRC);
+ }
+
+ fn new() -> Self {
+ unreachable!()
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ task: Task::default(),
+ proxy_ctx: StdMutex::new(None),
+ dataqueue: StdMutex::new(None),
+ settings: StdMutex::new(SettingsSrc::default()),
+ }
+ }
+}
+
+impl ObjectImpl for ProxySrc {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES_SRC[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("max-size-buffers", ..) => {
+ settings.max_size_buffers = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-size-bytes", ..) => {
+ settings.max_size_bytes = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-size-time", ..) => {
+ settings.max_size_time = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("proxy-context", ..) => {
+ settings.proxy_context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES_SRC[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("max-size-buffers", ..) => Ok(settings.max_size_buffers.to_value()),
+ subclass::Property("max-size-bytes", ..) => Ok(settings.max_size_bytes.to_value()),
+ subclass::Property("max-size-time", ..) => Ok(settings.max_size_time.to_value()),
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ subclass::Property("proxy-context", ..) => Ok(settings.proxy_context.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SOURCE);
+ }
+}
+
+impl ElementImpl for ProxySrc {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(SRC_CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ self.pause(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToPlaying => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-proxysink",
+ gst::Rank::None,
+ ProxySink::get_type(),
+ )?;
+ gst::Element::register(
+ Some(plugin),
+ "ts-proxysrc",
+ gst::Rank::None,
+ ProxySrc::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/queue.rs b/generic/gst-plugin-threadshare/src/queue.rs
new file mode 100644
index 000000000..7c02602ff
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/queue.rs
@@ -0,0 +1,867 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::channel::oneshot;
+use futures::future::BoxFuture;
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_log, gst_trace};
+
+use lazy_static::lazy_static;
+
+use std::collections::VecDeque;
+use std::sync::Mutex as StdMutex;
+use std::{u32, u64};
+
+use crate::runtime::prelude::*;
+use crate::runtime::{Context, PadSink, PadSinkRef, PadSrc, PadSrcRef, Task};
+
+use super::dataqueue::{DataQueue, DataQueueItem, DataQueueState};
+
+const DEFAULT_MAX_SIZE_BUFFERS: u32 = 200;
+const DEFAULT_MAX_SIZE_BYTES: u32 = 1024 * 1024;
+const DEFAULT_MAX_SIZE_TIME: u64 = gst::SECOND_VAL;
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ max_size_buffers: u32,
+ max_size_bytes: u32,
+ max_size_time: u64,
+ context: String,
+ context_wait: u32,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ max_size_buffers: DEFAULT_MAX_SIZE_BUFFERS,
+ max_size_bytes: DEFAULT_MAX_SIZE_BYTES,
+ max_size_time: DEFAULT_MAX_SIZE_TIME,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 5] = [
+ subclass::Property("max-size-buffers", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max Size Buffers",
+ "Maximum number of buffers to queue (0=unlimited)",
+ 0,
+ u32::MAX,
+ DEFAULT_MAX_SIZE_BUFFERS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-size-bytes", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Max Size Bytes",
+ "Maximum number of bytes to queue (0=unlimited)",
+ 0,
+ u32::MAX,
+ DEFAULT_MAX_SIZE_BYTES,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("max-size-time", |name| {
+ glib::ParamSpec::uint64(
+ name,
+ "Max Size Time",
+ "Maximum number of nanoseconds to queue (0=unlimited)",
+ 0,
+ u64::MAX - 1,
+ DEFAULT_MAX_SIZE_TIME,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Debug)]
+struct PendingQueue {
+ more_queue_space_sender: Option<oneshot::Sender<()>>,
+ scheduled: bool,
+ items: VecDeque<DataQueueItem>,
+}
+
+impl PendingQueue {
+ fn notify_more_queue_space(&mut self) {
+ self.more_queue_space_sender.take();
+ }
+}
+
+#[derive(Clone)]
+struct QueuePadSinkHandler;
+
+impl PadSinkHandler for QueuePadSinkHandler {
+ type ElementImpl = Queue;
+
+ fn sink_chain(
+ &self,
+ pad: &PadSinkRef,
+ _queue: &Queue,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer);
+ let queue = Queue::from_instance(&element);
+ queue
+ .enqueue_item(&element, DataQueueItem::Buffer(buffer))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ pad: &PadSinkRef,
+ _queue: &Queue,
+ element: &gst::Element,
+ list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", list);
+ let queue = Queue::from_instance(&element);
+ queue
+ .enqueue_item(&element, DataQueueItem::BufferList(list))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ pad: &PadSinkRef,
+ queue: &Queue,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_debug!(CAT, obj: pad.gst_pad(), "Handling non-serialized {:?}", event);
+
+ if let EventView::FlushStart(..) = event.view() {
+ queue.stop(&element).unwrap();
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding non-serialized {:?}", event);
+ queue.src_pad.gst_pad().push_event(event)
+ }
+
+ fn sink_event_serialized(
+ &self,
+ pad: &PadSinkRef,
+ _queue: &Queue,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling serialized {:?}", event);
+
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ let queue = Queue::from_instance(&element);
+
+ if let EventView::FlushStop(..) = event.view() {
+ queue.flush_stop(&element);
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Queuing serialized {:?}", event);
+ queue
+ .enqueue_item(&element, DataQueueItem::Event(event))
+ .await
+ .is_ok()
+ }
+ .boxed()
+ }
+
+ fn sink_query(
+ &self,
+ pad: &PadSinkRef,
+ queue: &Queue,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+
+ if query.is_serialized() {
+ // FIXME: How can we do this?
+ gst_log!(CAT, obj: pad.gst_pad(), "Dropping serialized {:?}", query);
+ false
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", query);
+ queue.src_pad.gst_pad().peer_query(query)
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct QueuePadSrcHandler;
+
+impl QueuePadSrcHandler {
+ async fn push_item(
+ pad: &PadSrcRef<'_>,
+ element: &gst::Element,
+ item: DataQueueItem,
+ ) -> Result<(), gst::FlowError> {
+ let queue = Queue::from_instance(element);
+
+ if let Some(pending_queue) = queue.pending_queue.lock().unwrap().as_mut() {
+ pending_queue.notify_more_queue_space();
+ }
+
+ match item {
+ DataQueueItem::Buffer(buffer) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", buffer);
+ pad.push(buffer).await.map(drop)
+ }
+ DataQueueItem::BufferList(list) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", list);
+ pad.push_list(list).await.map(drop)
+ }
+ DataQueueItem::Event(event) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ pad.push_event(event).await;
+ Ok(())
+ }
+ }
+ }
+}
+
+impl PadSrcHandler for QueuePadSrcHandler {
+ type ElementImpl = Queue;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ queue: &Queue,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ match event.view() {
+ EventView::FlushStart(..) => queue.stop(element).unwrap(),
+ EventView::FlushStop(..) => queue.flush_stop(element),
+ _ => (),
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event);
+ queue.sink_pad.gst_pad().push_event(event)
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ queue: &Queue,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+
+ if let QueryView::Scheduling(ref mut q) = query.view_mut() {
+ let mut new_query = gst::Query::new_scheduling();
+ let res = queue.sink_pad.gst_pad().peer_query(&mut new_query);
+ if !res {
+ return res;
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Upstream returned {:?}", new_query);
+
+ let (flags, min, max, align) = new_query.get_result();
+ q.set(flags, min, max, align);
+ q.add_scheduling_modes(
+ &new_query
+ .get_scheduling_modes()
+ .iter()
+ .cloned()
+ .filter(|m| m != &gst::PadMode::Pull)
+ .collect::<Vec<_>>(),
+ );
+ gst_log!(CAT, obj: pad.gst_pad(), "Returning {:?}", q.get_mut_query());
+ return true;
+ }
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", query);
+ queue.sink_pad.gst_pad().peer_query(query)
+ }
+}
+
+#[derive(Debug)]
+struct Queue {
+ sink_pad: PadSink,
+ src_pad: PadSrc,
+ task: Task,
+ dataqueue: StdMutex<Option<DataQueue>>,
+ pending_queue: StdMutex<Option<PendingQueue>>,
+ last_res: StdMutex<Result<gst::FlowSuccess, gst::FlowError>>,
+ settings: StdMutex<Settings>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-queue",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing queue"),
+ );
+}
+
+impl Queue {
+ /* Try transfering all the items from the pending queue to the DataQueue, then
+ * the current item. Errors out if the DataQueue was full, or the pending queue
+ * is already scheduled, in which case the current item should be added to the
+ * pending queue */
+ fn queue_until_full(
+ &self,
+ dataqueue: &DataQueue,
+ pending_queue: &mut Option<PendingQueue>,
+ item: DataQueueItem,
+ ) -> Result<(), DataQueueItem> {
+ match pending_queue {
+ None => dataqueue.push(item),
+ Some(PendingQueue {
+ scheduled: false,
+ ref mut items,
+ ..
+ }) => {
+ let mut failed_item = None;
+ while let Some(item) = items.pop_front() {
+ if let Err(item) = dataqueue.push(item) {
+ failed_item = Some(item);
+ }
+ }
+
+ if let Some(failed_item) = failed_item {
+ items.push_front(failed_item);
+
+ Err(item)
+ } else {
+ dataqueue.push(item)
+ }
+ }
+ _ => Err(item),
+ }
+ }
+
+ /* Schedules emptying of the pending queue. If there is an upstream
+ * TaskContext, the new task is spawned, it is otherwise
+ * returned, for the caller to block on */
+ fn schedule_pending_queue(
+ &self,
+ element: &gst::Element,
+ pending_queue: &mut Option<PendingQueue>,
+ ) -> impl Future<Output = ()> {
+ gst_log!(CAT, obj: element, "Scheduling pending queue now");
+
+ pending_queue.as_mut().unwrap().scheduled = true;
+
+ let element = element.clone();
+ async move {
+ let queue = Self::from_instance(&element);
+
+ loop {
+ let more_queue_space_receiver = {
+ let dataqueue = queue.dataqueue.lock().unwrap();
+ if dataqueue.is_none() {
+ return;
+ }
+ let mut pending_queue_grd = queue.pending_queue.lock().unwrap();
+
+ gst_log!(CAT, obj: &element, "Trying to empty pending queue");
+
+ if let Some(pending_queue) = pending_queue_grd.as_mut() {
+ let mut failed_item = None;
+ while let Some(item) = pending_queue.items.pop_front() {
+ if let Err(item) = dataqueue.as_ref().unwrap().push(item) {
+ failed_item = Some(item);
+ }
+ }
+
+ if let Some(failed_item) = failed_item {
+ pending_queue.items.push_front(failed_item);
+ let (sender, receiver) = oneshot::channel();
+ pending_queue.more_queue_space_sender = Some(sender);
+
+ receiver
+ } else {
+ gst_log!(CAT, obj: &element, "Pending queue is empty now");
+ *pending_queue_grd = None;
+ return;
+ }
+ } else {
+ gst_log!(CAT, obj: &element, "Flushing, dropping pending queue");
+ return;
+ }
+ };
+
+ gst_log!(CAT, obj: &element, "Waiting for more queue space");
+ let _ = more_queue_space_receiver.await;
+ }
+ }
+ }
+
+ async fn enqueue_item(
+ &self,
+ element: &gst::Element,
+ item: DataQueueItem,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let wait_fut = {
+ let dataqueue = self.dataqueue.lock().unwrap();
+ let dataqueue = dataqueue.as_ref().ok_or_else(|| {
+ gst_error!(CAT, obj: element, "No DataQueue");
+ gst::FlowError::Error
+ })?;
+
+ let mut pending_queue = self.pending_queue.lock().unwrap();
+
+ if let Err(item) = self.queue_until_full(&dataqueue, &mut pending_queue, item) {
+ if pending_queue
+ .as_ref()
+ .map(|pq| !pq.scheduled)
+ .unwrap_or(true)
+ {
+ if pending_queue.is_none() {
+ *pending_queue = Some(PendingQueue {
+ more_queue_space_sender: None,
+ scheduled: false,
+ items: VecDeque::new(),
+ });
+ }
+
+ let schedule_now = match item {
+ DataQueueItem::Event(ref ev) if ev.get_type() != gst::EventType::Eos => {
+ false
+ }
+ _ => true,
+ };
+
+ pending_queue.as_mut().unwrap().items.push_back(item);
+
+ gst_log!(
+ CAT,
+ obj: element,
+ "Queue is full - Pushing first item on pending queue"
+ );
+
+ if schedule_now {
+ let wait_fut = self.schedule_pending_queue(element, &mut pending_queue);
+ Some(wait_fut)
+ } else {
+ gst_log!(CAT, obj: element, "Scheduling pending queue later");
+ None
+ }
+ } else {
+ pending_queue.as_mut().unwrap().items.push_back(item);
+ None
+ }
+ } else {
+ None
+ }
+ };
+
+ if let Some(wait_fut) = wait_fut {
+ gst_log!(CAT, obj: element, "Blocking until queue has space again");
+ wait_fut.await;
+ }
+
+ *self.last_res.lock().unwrap()
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ let settings = self.settings.lock().unwrap().clone();
+
+ let dataqueue = DataQueue::new(
+ &element.clone().upcast(),
+ self.src_pad.gst_pad(),
+ if settings.max_size_buffers == 0 {
+ None
+ } else {
+ Some(settings.max_size_buffers)
+ },
+ if settings.max_size_bytes == 0 {
+ None
+ } else {
+ Some(settings.max_size_bytes)
+ },
+ if settings.max_size_time == 0 {
+ None
+ } else {
+ Some(settings.max_size_time)
+ },
+ );
+
+ *self.dataqueue.lock().unwrap() = Some(dataqueue);
+
+ let context =
+ Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+
+ self.src_pad.prepare(&QueuePadSrcHandler);
+ self.sink_pad.prepare(&QueuePadSinkHandler);
+
+ gst_debug!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ self.sink_pad.unprepare();
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ *self.dataqueue.lock().unwrap() = None;
+ *self.pending_queue.lock().unwrap() = None;
+
+ *self.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok);
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ let dataqueue = self.dataqueue.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Stopping");
+
+ *self.last_res.lock().unwrap() = Err(gst::FlowError::Flushing);
+
+ self.task.stop();
+
+ if let Some(dataqueue) = dataqueue.as_ref() {
+ dataqueue.pause();
+ dataqueue.clear();
+ dataqueue.stop();
+ }
+
+ if let Some(mut pending_queue) = self.pending_queue.lock().unwrap().take() {
+ pending_queue.notify_more_queue_space();
+ }
+
+ gst_debug!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let dataqueue = self.dataqueue.lock().unwrap();
+ let dataqueue = dataqueue.as_ref().unwrap();
+ if dataqueue.state() == DataQueueState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return Ok(());
+ }
+
+ gst_debug!(CAT, obj: element, "Starting");
+
+ dataqueue.start();
+ *self.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok);
+
+ self.start_task(element, dataqueue);
+
+ gst_debug!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_task(&self, element: &gst::Element, dataqueue: &DataQueue) {
+ let pad_weak = self.src_pad.downgrade();
+ let dataqueue = dataqueue.clone();
+ let element = element.clone();
+
+ self.task.start(move || {
+ let pad_weak = pad_weak.clone();
+ let mut dataqueue = dataqueue.clone();
+ let element = element.clone();
+
+ async move {
+ let item = dataqueue.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+ let item = match item {
+ Some(item) => item,
+ None => {
+ gst_log!(CAT, obj: pad.gst_pad(), "DataQueue Stopped");
+ return glib::Continue(false);
+ }
+ };
+
+ let queue = Queue::from_instance(&element);
+
+ match QueuePadSrcHandler::push_item(&pad, &element, item).await {
+ Ok(()) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Successfully pushed item");
+ *queue.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok);
+ glib::Continue(true)
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Flushing");
+ *queue.last_res.lock().unwrap() = Err(gst::FlowError::Flushing);
+ glib::Continue(false)
+ }
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "EOS");
+ *queue.last_res.lock().unwrap() = Err(gst::FlowError::Eos);
+ let eos = gst::Event::new_eos().build();
+ pad.push_event(eos).await;
+ glib::Continue(false)
+ }
+ Err(err) => {
+ gst_error!(CAT, obj: pad.gst_pad(), "Got error {}", err);
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ *queue.last_res.lock().unwrap() = Err(err);
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ // Keep the lock on the `dataqueue` until `flush_stop` is complete
+ // so as to prevent race conditions due to concurrent state transitions.
+ // Note that this won't deadlock as `Queue::dataqueue` guards a pointer to
+ // the `dataqueue` used within the `src_pad`'s `Task`.
+ let dataqueue = self.dataqueue.lock().unwrap();
+ let dataqueue = dataqueue.as_ref().unwrap();
+ if dataqueue.state() == DataQueueState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(CAT, obj: element, "Stopping Flush");
+
+ dataqueue.start();
+ self.start_task(element, dataqueue);
+
+ gst_debug!(CAT, obj: element, "Stopped Flush");
+ }
+}
+
+impl ObjectSubclass for Queue {
+ const NAME: &'static str = "RsTsQueue";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing queue",
+ "Generic",
+ "Simple data queue",
+ "Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ sink_pad: PadSink::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("sink").unwrap(),
+ Some("sink"),
+ )),
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ task: Task::default(),
+ dataqueue: StdMutex::new(None),
+ pending_queue: StdMutex::new(None),
+ last_res: StdMutex::new(Ok(gst::FlowSuccess::Ok)),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for Queue {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("max-size-buffers", ..) => {
+ settings.max_size_buffers = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-size-bytes", ..) => {
+ settings.max_size_bytes = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("max-size-time", ..) => {
+ settings.max_size_time = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("max-size-buffers", ..) => Ok(settings.max_size_buffers.to_value()),
+ subclass::Property("max-size-bytes", ..) => Ok(settings.max_size_bytes.to_value()),
+ subclass::Property("max-size-time", ..) => Ok(settings.max_size_time.to_value()),
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.sink_pad.gst_pad()).unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+ }
+}
+
+impl ElementImpl for Queue {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let success = self.parent_change_state(element, transition)?;
+
+ if transition == gst::StateChange::ReadyToPaused {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(Some(plugin), "ts-queue", gst::Rank::None, Queue::get_type())
+}
diff --git a/generic/gst-plugin-threadshare/src/runtime/executor.rs b/generic/gst-plugin-threadshare/src/runtime/executor.rs
new file mode 100644
index 000000000..9ee0ce617
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/runtime/executor.rs
@@ -0,0 +1,853 @@
+// Copyright (C) 2018-2020 Sebastian Dröge <sebastian@centricular.com>
+// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! The `Executor` for the `threadshare` GStreamer plugins framework.
+//!
+//! The [`threadshare`]'s `Executor` consists in a set of [`Context`]s. Each [`Context`] is
+//! identified by a `name` and runs a loop in a dedicated `thread`. Users can use the [`Context`]
+//! to spawn `Future`s. `Future`s are asynchronous processings which allow waiting for resources
+//! in a non-blocking way. Examples of non-blocking operations are:
+//!
+//! * Waiting for an incoming packet on a Socket.
+//! * Waiting for an asynchronous `Mutex` `lock` to succeed.
+//! * Waiting for a time related `Future`.
+//!
+//! `Element` implementations should use [`PadSrc`] & [`PadSink`] which provides high-level features.
+//!
+//! [`threadshare`]: ../../index.html
+//! [`Context`]: struct.Context.html
+//! [`PadSrc`]: ../pad/struct.PadSrc.html
+//! [`PadSink`]: ../pad/struct.PadSink.html
+
+use futures::channel::oneshot;
+use futures::future::BoxFuture;
+use futures::prelude::*;
+
+use gst;
+use gst::{gst_debug, gst_log, gst_trace, gst_warning};
+
+use lazy_static::lazy_static;
+
+use std::cell::RefCell;
+use std::collections::{HashMap, VecDeque};
+use std::fmt;
+use std::io;
+use std::mem;
+use std::pin::Pin;
+use std::sync::mpsc as sync_mpsc;
+use std::sync::{Arc, Mutex, Weak};
+use std::task::Poll;
+use std::thread;
+use std::time::Duration;
+
+use super::RUNTIME_CAT;
+
+// We are bound to using `sync` for the `runtime` `Mutex`es. Attempts to use `async` `Mutex`es
+// lead to the following issues:
+//
+// * `CONTEXTS`: can't `spawn` a `Future` when called from a `Context` thread via `ffi`.
+// * `timers`: can't automatically `remove` the timer from `BinaryHeap` because `async drop`
+// is not available.
+// * `task_queues`: can't `add` a pending task when called from a `Context` thread via `ffi`.
+//
+// Also, we want to be able to `acquire` a `Context` outside of an `async` context.
+// These `Mutex`es must be `lock`ed for a short period.
+lazy_static! {
+ static ref CONTEXTS: Mutex<HashMap<String, Weak<ContextInner>>> = Mutex::new(HashMap::new());
+}
+
+thread_local!(static CURRENT_THREAD_CONTEXT: RefCell<Option<ContextWeak>> = RefCell::new(None));
+
+tokio::task_local! {
+ static CURRENT_TASK_ID: TaskId;
+}
+
+/// Blocks on `future` in one way or another if possible.
+///
+/// IO & time related `Future`s must be handled within their own [`Context`].
+/// Wait for the result using a [`JoinHandle`] or a `channel`.
+///
+/// If there's currently an active `Context` with a task, then the future is only queued up as a
+/// pending sub task for that task.
+///
+/// Otherwise the current thread is blocking and the passed in future is executed.
+///
+/// Note that you must not pass any futures here that wait for the currently active task in one way
+/// or another as this would deadlock!
+pub fn block_on_or_add_sub_task<Fut: Future + Send + 'static>(future: Fut) -> Option<Fut::Output> {
+ if let Some((cur_context, cur_task_id)) = Context::current_task() {
+ gst_debug!(
+ RUNTIME_CAT,
+ "Adding subtask to task {:?} on context {}",
+ cur_task_id,
+ cur_context.name()
+ );
+ let _ = Context::add_sub_task(async move {
+ future.await;
+ Ok(())
+ });
+ return None;
+ }
+
+ // Not running in a Context thread so we can block
+ Some(block_on(future))
+}
+
+/// Blocks on `future`.
+///
+/// IO & time related `Future`s must be handled within their own [`Context`].
+/// Wait for the result using a [`JoinHandle`] or a `channel`.
+///
+/// The current thread is blocking and the passed in future is executed.
+///
+/// # Panics
+///
+/// This function panics if called within a [`Context`] thread.
+pub fn block_on<Fut: Future>(future: Fut) -> Fut::Output {
+ assert!(!Context::is_context_thread());
+
+ // Not running in a Context thread so we can block
+ gst_debug!(RUNTIME_CAT, "Blocking on new dummy context");
+
+ let context = Context(Arc::new(ContextInner {
+ real: None,
+ task_queues: Mutex::new((0, HashMap::new())),
+ }));
+
+ CURRENT_THREAD_CONTEXT.with(move |cur_ctx| {
+ *cur_ctx.borrow_mut() = Some(context.downgrade());
+
+ let res = futures::executor::block_on(async move {
+ CURRENT_TASK_ID
+ .scope(TaskId(0), async move {
+ let task_id = CURRENT_TASK_ID.try_with(|task_id| *task_id).ok();
+ assert_eq!(task_id, Some(TaskId(0)));
+
+ let res = future.await;
+
+ while Context::current_has_sub_tasks() {
+ if Context::drain_sub_tasks().await.is_err() {
+ break;
+ }
+ }
+
+ res
+ })
+ .await
+ });
+
+ *cur_ctx.borrow_mut() = None;
+
+ res
+ })
+}
+
+/// Yields execution back to the runtime
+#[inline]
+pub async fn yield_now() {
+ tokio::task::yield_now().await;
+}
+
+struct ContextThread {
+ name: String,
+}
+
+impl ContextThread {
+ fn start(name: &str, wait: u32) -> Context {
+ let context_thread = ContextThread { name: name.into() };
+ let (context_sender, context_receiver) = sync_mpsc::channel();
+ let join = thread::spawn(move || {
+ context_thread.spawn(wait, context_sender);
+ });
+
+ let context = context_receiver.recv().expect("Context thread init failed");
+ *context
+ .0
+ .real
+ .as_ref()
+ .unwrap()
+ .shutdown
+ .join
+ .lock()
+ .unwrap() = Some(join);
+
+ context
+ }
+
+ fn spawn(&self, wait: u32, context_sender: sync_mpsc::Sender<Context>) {
+ gst_debug!(RUNTIME_CAT, "Started context thread '{}'", self.name);
+
+ let mut runtime = tokio::runtime::Builder::new()
+ .basic_scheduler()
+ .thread_name(self.name.clone())
+ .enable_all()
+ .max_throttling(Duration::from_millis(wait as u64))
+ .build()
+ .expect("Couldn't build the runtime");
+
+ let (shutdown_sender, shutdown_receiver) = oneshot::channel();
+
+ let shutdown = ContextShutdown {
+ name: self.name.clone(),
+ shutdown: Some(shutdown_sender),
+ join: Mutex::new(None),
+ };
+
+ let context = Context(Arc::new(ContextInner {
+ real: Some(ContextRealInner {
+ name: self.name.clone(),
+ handle: Mutex::new(runtime.handle().clone()),
+ shutdown,
+ }),
+ task_queues: Mutex::new((0, HashMap::new())),
+ }));
+
+ CURRENT_THREAD_CONTEXT.with(|cur_ctx| {
+ *cur_ctx.borrow_mut() = Some(context.downgrade());
+ });
+
+ context_sender.send(context).unwrap();
+
+ let _ = runtime.block_on(shutdown_receiver);
+ }
+}
+
+impl Drop for ContextThread {
+ fn drop(&mut self) {
+ gst_debug!(RUNTIME_CAT, "Terminated: context thread '{}'", self.name);
+ }
+}
+
+#[derive(Debug)]
+struct ContextShutdown {
+ name: String,
+ shutdown: Option<oneshot::Sender<()>>,
+ join: Mutex<Option<thread::JoinHandle<()>>>,
+}
+
+impl Drop for ContextShutdown {
+ fn drop(&mut self) {
+ gst_debug!(
+ RUNTIME_CAT,
+ "Shutting down context thread thread '{}'",
+ self.name
+ );
+ self.shutdown.take().unwrap();
+
+ gst_trace!(
+ RUNTIME_CAT,
+ "Waiting for context thread '{}' to shutdown",
+ self.name
+ );
+ let join_handle = self.join.lock().unwrap().take().unwrap();
+ let _ = join_handle.join();
+ }
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
+pub struct TaskId(u64);
+
+pub type SubTaskOutput = Result<(), gst::FlowError>;
+pub struct SubTaskQueue(VecDeque<BoxFuture<'static, SubTaskOutput>>);
+
+impl fmt::Debug for SubTaskQueue {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_tuple("SubTaskQueue").finish()
+ }
+}
+
+pub struct JoinError(tokio::task::JoinError);
+
+impl fmt::Display for JoinError {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&self.0, fmt)
+ }
+}
+
+impl fmt::Debug for JoinError {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Debug::fmt(&self.0, fmt)
+ }
+}
+
+impl std::error::Error for JoinError {}
+
+impl From<tokio::task::JoinError> for JoinError {
+ fn from(src: tokio::task::JoinError) -> Self {
+ JoinError(src)
+ }
+}
+
+/// Wrapper for the underlying runtime JoinHandle implementation.
+pub struct JoinHandle<T> {
+ join_handle: tokio::task::JoinHandle<T>,
+ context: ContextWeak,
+ task_id: TaskId,
+}
+
+unsafe impl<T: Send> Send for JoinHandle<T> {}
+unsafe impl<T: Send> Sync for JoinHandle<T> {}
+
+impl<T> JoinHandle<T> {
+ pub fn is_current(&self) -> bool {
+ if let Some((context, task_id)) = Context::current_task() {
+ let self_context = self.context.upgrade();
+ self_context.map(|c| c == context).unwrap_or(false) && task_id == self.task_id
+ } else {
+ false
+ }
+ }
+
+ pub fn context(&self) -> Option<Context> {
+ self.context.upgrade()
+ }
+
+ pub fn task_id(&self) -> TaskId {
+ self.task_id
+ }
+}
+
+impl<T> Unpin for JoinHandle<T> {}
+
+impl<T> Future for JoinHandle<T> {
+ type Output = Result<T, JoinError>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+ if self.as_ref().is_current() {
+ panic!("Trying to join task {:?} from itself", self.as_ref());
+ }
+
+ self.as_mut()
+ .join_handle
+ .poll_unpin(cx)
+ .map_err(JoinError::from)
+ }
+}
+
+impl<T> fmt::Debug for JoinHandle<T> {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let context_name = self.context.upgrade().map(|c| String::from(c.name()));
+
+ fmt.debug_struct("JoinHandle")
+ .field("context", &context_name)
+ .field("task_id", &self.task_id)
+ .finish()
+ }
+}
+
+#[derive(Debug)]
+struct ContextRealInner {
+ name: String,
+ handle: Mutex<tokio::runtime::Handle>,
+ // Only used for dropping
+ shutdown: ContextShutdown,
+}
+
+#[derive(Debug)]
+struct ContextInner {
+ // Otherwise a dummy context
+ real: Option<ContextRealInner>,
+ task_queues: Mutex<(u64, HashMap<u64, SubTaskQueue>)>,
+}
+
+impl Drop for ContextInner {
+ fn drop(&mut self) {
+ if let Some(ref real) = self.real {
+ let mut contexts = CONTEXTS.lock().unwrap();
+ gst_debug!(RUNTIME_CAT, "Finalizing context '{}'", real.name);
+ contexts.remove(&real.name);
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct ContextWeak(Weak<ContextInner>);
+
+impl ContextWeak {
+ pub fn upgrade(&self) -> Option<Context> {
+ self.0.upgrade().map(Context)
+ }
+}
+
+/// A `threadshare` `runtime` `Context`.
+///
+/// The `Context` provides low-level asynchronous processing features to
+/// multiplex task execution on a single thread.
+///
+/// `Element` implementations should use [`PadSrc`] and [`PadSink`] which
+/// provide high-level features.
+///
+/// See the [module-level documentation](index.html) for more.
+///
+/// [`PadSrc`]: ../pad/struct.PadSrc.html
+/// [`PadSink`]: ../pad/struct.PadSink.html
+#[derive(Clone, Debug)]
+pub struct Context(Arc<ContextInner>);
+
+impl PartialEq for Context {
+ fn eq(&self, other: &Self) -> bool {
+ Arc::ptr_eq(&self.0, &other.0)
+ }
+}
+
+impl Eq for Context {}
+
+impl Context {
+ pub fn acquire(context_name: &str, wait: u32) -> Result<Self, io::Error> {
+ assert_ne!(context_name, "DUMMY");
+
+ let mut contexts = CONTEXTS.lock().unwrap();
+
+ if let Some(inner_weak) = contexts.get(context_name) {
+ if let Some(inner_strong) = inner_weak.upgrade() {
+ gst_debug!(
+ RUNTIME_CAT,
+ "Joining Context '{}'",
+ inner_strong.real.as_ref().unwrap().name
+ );
+ return Ok(Context(inner_strong));
+ }
+ }
+
+ let context = ContextThread::start(context_name, wait);
+ contexts.insert(context_name.into(), Arc::downgrade(&context.0));
+
+ gst_debug!(
+ RUNTIME_CAT,
+ "New Context '{}'",
+ context.0.real.as_ref().unwrap().name
+ );
+ Ok(context)
+ }
+
+ pub fn downgrade(&self) -> ContextWeak {
+ ContextWeak(Arc::downgrade(&self.0))
+ }
+
+ pub fn name(&self) -> &str {
+ match self.0.real {
+ Some(ref real) => real.name.as_str(),
+ None => "DUMMY",
+ }
+ }
+
+ /// Returns `true` if a `Context` is running on current thread.
+ pub fn is_context_thread() -> bool {
+ CURRENT_THREAD_CONTEXT.with(|cur_ctx| cur_ctx.borrow().is_some())
+ }
+
+ /// Returns the `Context` running on current thread, if any.
+ pub fn current() -> Option<Context> {
+ CURRENT_THREAD_CONTEXT.with(|cur_ctx| {
+ cur_ctx
+ .borrow()
+ .as_ref()
+ .and_then(|ctx_weak| ctx_weak.upgrade())
+ })
+ }
+
+ /// Returns the `TaskId` running on current thread, if any.
+ pub fn current_task() -> Option<(Context, TaskId)> {
+ CURRENT_THREAD_CONTEXT.with(|cur_ctx| {
+ cur_ctx
+ .borrow()
+ .as_ref()
+ .and_then(|ctx_weak| ctx_weak.upgrade())
+ .and_then(|ctx| {
+ let task_id = CURRENT_TASK_ID.try_with(|task_id| *task_id).ok();
+
+ task_id.map(move |task_id| (ctx, task_id))
+ })
+ })
+ }
+
+ pub fn enter<F, R>(&self, f: F) -> R
+ where
+ F: FnOnce() -> R,
+ {
+ let real = match self.0.real {
+ Some(ref real) => real,
+ None => panic!("Can't enter on dummy context"),
+ };
+
+ real.handle.lock().unwrap().enter(f)
+ }
+
+ pub fn spawn<Fut>(&self, future: Fut) -> JoinHandle<Fut::Output>
+ where
+ Fut: Future + Send + 'static,
+ Fut::Output: Send + 'static,
+ {
+ let real = match self.0.real {
+ Some(ref real) => real,
+ None => panic!("Can't spawn new tasks on dummy context"),
+ };
+
+ let mut task_queues = self.0.task_queues.lock().unwrap();
+ let id = task_queues.0;
+ task_queues.0 += 1;
+ task_queues.1.insert(id, SubTaskQueue(VecDeque::new()));
+
+ let id = TaskId(id);
+ gst_trace!(
+ RUNTIME_CAT,
+ "Spawning new task {:?} on context {}",
+ id,
+ real.name
+ );
+
+ let join_handle = real.handle.lock().unwrap().spawn(async move {
+ let ctx = Context::current().unwrap();
+ let real = ctx.0.real.as_ref().unwrap();
+
+ gst_trace!(
+ RUNTIME_CAT,
+ "Running task {:?} on context {}",
+ id,
+ real.name
+ );
+ let res = CURRENT_TASK_ID.scope(id, future).await;
+
+ // Remove task from the list
+ {
+ let mut task_queues = ctx.0.task_queues.lock().unwrap();
+ if let Some(task_queue) = task_queues.1.remove(&id.0) {
+ let l = task_queue.0.len();
+ if l > 0 {
+ gst_warning!(
+ RUNTIME_CAT,
+ "Task {:?} on context {} has {} pending sub tasks",
+ id,
+ real.name,
+ l
+ );
+ }
+ }
+ }
+
+ gst_trace!(RUNTIME_CAT, "Task {:?} on context {} done", id, real.name);
+
+ res
+ });
+
+ JoinHandle {
+ join_handle,
+ context: self.downgrade(),
+ task_id: id,
+ }
+ }
+
+ pub fn current_has_sub_tasks() -> bool {
+ let (ctx, task_id) = match Context::current_task() {
+ Some(task) => task,
+ None => {
+ gst_trace!(RUNTIME_CAT, "No current task");
+ return false;
+ }
+ };
+
+ let task_queues = ctx.0.task_queues.lock().unwrap();
+ task_queues
+ .1
+ .get(&task_id.0)
+ .map(|t| !t.0.is_empty())
+ .unwrap_or(false)
+ }
+
+ pub fn add_sub_task<T>(sub_task: T) -> Result<(), T>
+ where
+ T: Future<Output = SubTaskOutput> + Send + 'static,
+ {
+ let (ctx, task_id) = match Context::current_task() {
+ Some(task) => task,
+ None => {
+ gst_trace!(RUNTIME_CAT, "No current task");
+ return Err(sub_task);
+ }
+ };
+
+ let mut task_queues = ctx.0.task_queues.lock().unwrap();
+ match task_queues.1.get_mut(&task_id.0) {
+ Some(task_queue) => {
+ if let Some(ref real) = ctx.0.real {
+ gst_trace!(
+ RUNTIME_CAT,
+ "Adding subtask to {:?} on context {}",
+ task_id,
+ real.name
+ );
+ } else {
+ gst_trace!(
+ RUNTIME_CAT,
+ "Adding subtask to {:?} on dummy context",
+ task_id,
+ );
+ }
+ task_queue.0.push_back(sub_task.boxed());
+ Ok(())
+ }
+ None => {
+ gst_trace!(RUNTIME_CAT, "Task was removed in the meantime");
+ Err(sub_task)
+ }
+ }
+ }
+
+ pub fn drain_sub_tasks() -> impl Future<Output = SubTaskOutput> + Send + 'static {
+ async {
+ let (ctx, task_id) = match Context::current_task() {
+ Some(task) => task,
+ None => return Ok(()),
+ };
+
+ ctx.drain_sub_tasks_internal(task_id).await
+ }
+ }
+
+ fn drain_sub_tasks_internal(
+ &self,
+ id: TaskId,
+ ) -> impl Future<Output = SubTaskOutput> + Send + 'static {
+ let mut task_queue = {
+ let mut task_queues = self.0.task_queues.lock().unwrap();
+ if let Some(task_queue) = task_queues.1.get_mut(&id.0) {
+ mem::replace(task_queue, SubTaskQueue(VecDeque::new()))
+ } else {
+ SubTaskQueue(VecDeque::new())
+ }
+ };
+
+ let name = self
+ .0
+ .real
+ .as_ref()
+ .map(|r| r.name.clone())
+ .unwrap_or_else(|| String::from("DUMMY"));
+ async move {
+ if !task_queue.0.is_empty() {
+ gst_log!(
+ RUNTIME_CAT,
+ "Scheduling draining {} sub tasks from {:?} on '{}'",
+ task_queue.0.len(),
+ id,
+ &name,
+ );
+
+ for task in task_queue.0.drain(..) {
+ task.await?;
+ }
+ }
+
+ Ok(())
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use futures;
+ use futures::channel::mpsc;
+ use futures::lock::Mutex;
+ use futures::prelude::*;
+
+ use gst;
+
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
+ use std::sync::Arc;
+ use std::time::{Duration, Instant};
+
+ use super::Context;
+
+ type Item = i32;
+
+ const SLEEP_DURATION_MS: u32 = 2;
+ const SLEEP_DURATION: Duration = Duration::from_millis(SLEEP_DURATION_MS as u64);
+ const DELAY: Duration = Duration::from_millis(SLEEP_DURATION_MS as u64 * 10);
+
+ #[tokio::test]
+ async fn drain_sub_tasks() {
+ // Setup
+ gst::init().unwrap();
+
+ let context = Context::acquire("drain_sub_tasks", SLEEP_DURATION_MS).unwrap();
+
+ let join_handle = context.spawn(async move {
+ let (sender, mut receiver) = mpsc::channel(1);
+ let sender: Arc<Mutex<mpsc::Sender<Item>>> = Arc::new(Mutex::new(sender));
+
+ let add_sub_task = move |item| {
+ let sender = sender.clone();
+ Context::add_sub_task(async move {
+ sender
+ .lock()
+ .await
+ .send(item)
+ .await
+ .map_err(|_| gst::FlowError::Error)
+ })
+ };
+
+ // Tests
+
+ // Drain empty queue
+ let drain_fut = Context::drain_sub_tasks();
+ drain_fut.await.unwrap();
+
+ // Add a subtask
+ add_sub_task(0).map_err(drop).unwrap();
+
+ // Check that it was not executed yet
+ receiver.try_next().unwrap_err();
+
+ // Drain it now and check that it was executed
+ let drain_fut = Context::drain_sub_tasks();
+ drain_fut.await.unwrap();
+ assert_eq!(receiver.try_next().unwrap(), Some(0));
+
+ // Add another task and check that it's not executed yet
+ add_sub_task(1).map_err(drop).unwrap();
+ receiver.try_next().unwrap_err();
+
+ // Return the receiver
+ receiver
+ });
+
+ let mut receiver = join_handle.await.unwrap();
+
+ // The last sub task should be simply dropped at this point
+ assert_eq!(receiver.try_next().unwrap(), None);
+ }
+
+ #[tokio::test]
+ async fn block_on_within_tokio() {
+ let context = Context::acquire("block_on_within_tokio", SLEEP_DURATION_MS).unwrap();
+
+ let bytes_sent = crate::runtime::executor::block_on(context.spawn(async {
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
+ let socket = UdpSocket::bind(saddr).unwrap();
+ let mut socket = tokio::net::UdpSocket::from_std(socket).unwrap();
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
+ socket.send_to(&[0; 10], saddr).await.unwrap()
+ }))
+ .unwrap();
+ assert_eq!(bytes_sent, 10);
+
+ let elapsed = crate::runtime::executor::block_on(context.spawn(async {
+ let now = Instant::now();
+ crate::runtime::time::delay_for(DELAY).await;
+ now.elapsed()
+ }))
+ .unwrap();
+ // Due to throttling, `Delay` may be fired earlier
+ assert!(elapsed + SLEEP_DURATION / 2 >= DELAY);
+ }
+
+ #[test]
+ fn block_on_from_sync() {
+ let context = Context::acquire("block_on_from_sync", SLEEP_DURATION_MS).unwrap();
+
+ let bytes_sent = crate::runtime::executor::block_on(context.spawn(async {
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001);
+ let socket = UdpSocket::bind(saddr).unwrap();
+ let mut socket = tokio::net::UdpSocket::from_std(socket).unwrap();
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
+ socket.send_to(&[0; 10], saddr).await.unwrap()
+ }))
+ .unwrap();
+ assert_eq!(bytes_sent, 10);
+
+ let elapsed = crate::runtime::executor::block_on(context.spawn(async {
+ let now = Instant::now();
+ crate::runtime::time::delay_for(DELAY).await;
+ now.elapsed()
+ }))
+ .unwrap();
+ // Due to throttling, `Delay` may be fired earlier
+ assert!(elapsed + SLEEP_DURATION / 2 >= DELAY);
+ }
+
+ #[test]
+ fn block_on_from_context() {
+ gst::init().unwrap();
+
+ let context = Context::acquire("block_on_from_context", SLEEP_DURATION_MS).unwrap();
+ let join_handle = context.spawn(async {
+ crate::runtime::executor::block_on(async {
+ crate::runtime::time::delay_for(DELAY).await;
+ });
+ });
+ // Panic: attempt to `runtime::executor::block_on` within a `Context` thread
+ futures::executor::block_on(join_handle).unwrap_err();
+ }
+
+ #[tokio::test]
+ async fn enter_context_from_tokio() {
+ gst::init().unwrap();
+
+ let context = Context::acquire("enter_context_from_tokio", SLEEP_DURATION_MS).unwrap();
+ let mut socket = context
+ .enter(|| {
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5002);
+ let socket = UdpSocket::bind(saddr).unwrap();
+ tokio::net::UdpSocket::from_std(socket)
+ })
+ .unwrap();
+
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
+ let bytes_sent = socket.send_to(&[0; 10], saddr).await.unwrap();
+ assert_eq!(bytes_sent, 10);
+
+ let elapsed = context.enter(|| {
+ futures::executor::block_on(async {
+ let now = Instant::now();
+ crate::runtime::time::delay_for(DELAY).await;
+ now.elapsed()
+ })
+ });
+ // Due to throttling, `Delay` may be fired earlier
+ assert!(elapsed + SLEEP_DURATION / 2 >= DELAY);
+ }
+
+ #[test]
+ fn enter_context_from_sync() {
+ gst::init().unwrap();
+
+ let context = Context::acquire("enter_context_from_sync", SLEEP_DURATION_MS).unwrap();
+ let mut socket = context
+ .enter(|| {
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5003);
+ let socket = UdpSocket::bind(saddr).unwrap();
+ tokio::net::UdpSocket::from_std(socket)
+ })
+ .unwrap();
+
+ let saddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
+ let bytes_sent = futures::executor::block_on(socket.send_to(&[0; 10], saddr)).unwrap();
+ assert_eq!(bytes_sent, 10);
+
+ let elapsed = context.enter(|| {
+ futures::executor::block_on(async {
+ let now = Instant::now();
+ crate::runtime::time::delay_for(DELAY).await;
+ now.elapsed()
+ })
+ });
+ // Due to throttling, `Delay` may be fired earlier
+ assert!(elapsed + SLEEP_DURATION / 2 >= DELAY);
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/runtime/mod.rs b/generic/gst-plugin-threadshare/src/runtime/mod.rs
new file mode 100644
index 000000000..39923a4e1
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/runtime/mod.rs
@@ -0,0 +1,70 @@
+// Copyright (C) 2019 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! A `runtime` for the `threadshare` GStreamer plugins framework.
+//!
+//! Many `GStreamer` `Element`s internally spawn OS `thread`s. For most applications, this is not an
+//! issue. However, in applications which process many `Stream`s in parallel, the high number of
+//! `threads` leads to reduced efficiency due to:
+//!
+//! * context switches,
+//! * scheduler overhead,
+//! * most of the threads waiting for some resources to be available.
+//!
+//! The `threadshare` `runtime` is a framework to build `Element`s for such applications. It
+//! uses light-weight threading to allow multiple `Element`s share a reduced number of OS `thread`s.
+//!
+//! See this [talk] ([slides]) for a presentation of the motivations and principles,
+//! and this [blog post].
+//!
+//! Current implementation uses the crate [`tokio`].
+//!
+//! Most `Element`s implementations should use the high-level features provided by [`PadSrc`] &
+//! [`PadSink`].
+//!
+//! [talk]: https://gstconf.ubicast.tv/videos/when-adding-more-threads-adds-more-problems-thread-sharing-between-elements-in-gstreamer/
+//! [slides]: https://gstreamer.freedesktop.org/data/events/gstreamer-conference/2018/Sebastian%20Dr%C3%B6ge%20-%20When%20adding%20more%20threads%20adds%20more%20problems:%20Thread-sharing%20between%20elements%20in%20GStreamer.pdf
+//! [blog post]: https://coaxion.net/blog/2018/04/improving-gstreamer-performance-on-a-high-number-of-network-streams-by-sharing-threads-between-elements-with-rusts-tokio-crate
+//! [`tokio`]: https://crates.io/crates/tokio
+//! [`PadSrc`]: pad/struct.PadSrc.html
+//! [`PadSink`]: pad/struct.PadSink.html
+
+pub mod executor;
+pub use executor::{Context, JoinHandle, SubTaskOutput};
+
+pub mod pad;
+pub use pad::{PadSink, PadSinkRef, PadSinkWeak, PadSrc, PadSrcRef, PadSrcWeak};
+
+pub mod task;
+pub use task::{Task, TaskState};
+
+pub mod prelude {
+ pub use super::pad::{PadSinkHandler, PadSrcHandler};
+}
+
+pub mod time;
+
+use gst;
+use lazy_static::lazy_static;
+
+lazy_static! {
+ static ref RUNTIME_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-runtime",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing Runtime"),
+ );
+}
diff --git a/generic/gst-plugin-threadshare/src/runtime/pad.rs b/generic/gst-plugin-threadshare/src/runtime/pad.rs
new file mode 100644
index 000000000..fde5ed3bf
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/runtime/pad.rs
@@ -0,0 +1,1130 @@
+// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
+// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! An implementation of `Pad`s to run asynchronous processings.
+//!
+//! [`PadSink`] & [`PadSrc`] provide an asynchronous API to ease the development of `Element`s in
+//! the `threadshare` GStreamer plugins framework.
+//!
+//! The diagram below shows how the [`PadSrc`] & [`PadSink`] and the related `struct`s integrate in
+//! `ts` `Element`s.
+//!
+//! Note: [`PadSrc`] & [`PadSink`] only support `gst::PadMode::Push` at the moment.
+//!
+//! ```text
+//! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+//! Element A ┃ ┃ Element B
+//! ┃ ┃
+//! ╭─────────────────╮ ┃ ┃ ╭──────────────────╮
+//! │ PadSrc │ ┃ ┃ │ PadSink │
+//! │ Handler │ ┃ ┃ │ Handler │
+//! │─────────────────│ ┃ ┃ │──────────────────│
+//! │ - src_activate* │ ╭──┸──╮ ╭──┸──╮ │ - sink_activate* │
+//! │ - src_event* │<────│ │<╌╌╌│ │───>│ - sink_chain* │
+//! │ - src_query │<────│ gst │ │ gst │───>│ - sink_event* │
+//! │─────────────────│ │ │ │ │───>│ - sink_query │
+//! │ - task fn │ │ Pad │ │ Pad │ ╰──────────────────╯
+//! ╰─────────────────╯ ╭─>│ │╌╌╌>│ │─╮ │
+//! ╭───────╯ │ │ ╰──┰──╯ ╰──┰──╯ ╰───────╮ │
+//! ╭────────────╮ ╭────────╮ push* │ ┃ ┃ ╭─────────╮
+//! │ Pad Task ↺ │<──│ PadSrc │───────╯ ┃ ┃ │ PadSink │
+//! ╰────────────╯ ╰────────╯ ┃ ┃ ╰─────────╯
+//! ━━━━━━━━━━━━━━━━━━━━━━│━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━│━━━━━━━━━━━━
+//! ╰───────────────────╮ ╭─────────────────╯
+//! ╭──────────────╮
+//! │ PadContext │
+//! │╭────────────╮│
+//! ││ Context ↺ ││
+//! ╰╰────────────╯╯
+//! ```
+//!
+//! Asynchronous operations for both [`PadSrc`] in `Element A` and [`PadSink`] in `Element B` run on
+//! the same [`Context`], which can also be shared by other `Element`s or instances of the same
+//! `Element`s in multiple `Pipeline`s.
+//!
+//! `Element A` & `Element B` can also be linked to non-threadshare `Element`s in which case, they
+//! operate in a regular synchronous way.
+//!
+//! Note that only operations on the streaming thread (serialized events, buffers, serialized
+//! queries) are handled from the `PadContext` and asynchronously, everything else operates
+//! blocking.
+//!
+//! [`PadSink`]: struct.PadSink.html
+//! [`PadSrc`]: struct.PadSrc.html
+//! [`Context`]: ../executor/struct.Context.html
+
+use futures::future;
+use futures::future::BoxFuture;
+use futures::prelude::*;
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_error, gst_fixme, gst_log, gst_loggable_error};
+use gst::{FlowError, FlowSuccess};
+
+use std::marker::PhantomData;
+use std::sync::{Arc, Weak};
+
+use super::executor::{block_on_or_add_sub_task, Context};
+use super::RUNTIME_CAT;
+
+#[inline]
+fn event_ret_to_event_full_res(
+ ret: bool,
+ event_type: gst::EventType,
+) -> Result<FlowSuccess, FlowError> {
+ if ret {
+ Ok(FlowSuccess::Ok)
+ } else if event_type == gst::EventType::Caps {
+ Err(FlowError::NotNegotiated)
+ } else {
+ Err(FlowError::Error)
+ }
+}
+
+#[inline]
+fn event_to_event_full(ret: bool, event_type: gst::EventType) -> Result<FlowSuccess, FlowError> {
+ event_ret_to_event_full_res(ret, event_type)
+}
+
+#[inline]
+fn event_to_event_full_serialized(
+ ret: BoxFuture<'static, bool>,
+ event_type: gst::EventType,
+) -> BoxFuture<'static, Result<FlowSuccess, FlowError>> {
+ ret.map(move |ret| event_ret_to_event_full_res(ret, event_type))
+ .boxed()
+}
+
+/// A trait to define `handler`s for [`PadSrc`] callbacks.
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSrc`]: struct.PadSrc.html
+/// [`pad` module]: index.html
+pub trait PadSrcHandler: Clone + Send + Sync + 'static {
+ type ElementImpl: ElementImpl + ObjectSubclass;
+
+ fn src_activate(
+ &self,
+ pad: &PadSrcRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ ) -> Result<(), gst::LoggableError> {
+ let gst_pad = pad.gst_pad();
+ if gst_pad.is_active() {
+ gst_debug!(
+ RUNTIME_CAT,
+ obj: gst_pad,
+ "Already activated in {:?} mode ",
+ gst_pad.get_mode()
+ );
+ return Ok(());
+ }
+
+ gst_pad
+ .activate_mode(gst::PadMode::Push, true)
+ .map_err(|err| {
+ gst_error!(
+ RUNTIME_CAT,
+ obj: gst_pad,
+ "Error in PadSrc activate: {:?}",
+ err
+ );
+ gst_loggable_error!(RUNTIME_CAT, "Error in PadSrc activate: {:?}", err)
+ })
+ }
+
+ fn src_activatemode(
+ &self,
+ _pad: &PadSrcRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ _mode: gst::PadMode,
+ _active: bool,
+ ) -> Result<(), gst::LoggableError> {
+ Ok(())
+ }
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ _imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+ pad.gst_pad().event_default(Some(element), event)
+ }
+
+ fn src_event_full(
+ &self,
+ pad: &PadSrcRef,
+ imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> Result<FlowSuccess, FlowError> {
+ // default is to dispatch to `src_event`
+ // (as implemented in `gst_pad_send_event_unchecked`)
+ let event_type = event.get_type();
+ event_to_event_full(self.src_event(pad, imp, element, event), event_type)
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ _imp: &Self::ElementImpl,
+ element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+ if query.is_serialized() {
+ // FIXME serialized queries should be handled with the dataflow
+ // but we can't return a `Future` because we couldn't honor QueryRef's lifetime
+ false
+ } else {
+ pad.gst_pad().query_default(Some(element), query)
+ }
+ }
+}
+
+#[derive(Debug)]
+struct PadSrcInner {
+ gst_pad: gst::Pad,
+}
+
+impl PadSrcInner {
+ fn new(gst_pad: gst::Pad) -> Self {
+ if gst_pad.get_direction() != gst::PadDirection::Src {
+ panic!("Wrong pad direction for PadSrc");
+ }
+
+ PadSrcInner { gst_pad }
+ }
+}
+
+/// A [`PadSrc`] which can be moved in [`handler`]s functions and `Future`s.
+///
+/// Call [`upgrade`] to use the [`PadSrc`].
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSrc`]: struct.PadSrc.html
+/// [`handler`]: trait.PadSrcHandler.html
+/// [`upgrade`]: struct.PadSrcWeak.html#method.upgrade
+/// [`pad` module]: index.html
+#[derive(Clone, Debug)]
+pub struct PadSrcWeak(Weak<PadSrcInner>);
+
+impl PadSrcWeak {
+ pub fn upgrade(&self) -> Option<PadSrcRef<'_>> {
+ self.0.upgrade().map(PadSrcRef::new)
+ }
+}
+
+/// A [`PadSrc`] to be used in `Handler`s functions and `Future`s.
+///
+/// Call [`downgrade`] if you need to `clone` the [`PadSrc`].
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSrc`]: struct.PadSrc.html
+/// [`PadSrcWeak`]: struct.PadSrcWeak.html
+/// [`downgrade`]: struct.PadSrcRef.html#method.downgrade
+/// [`pad` module]: index.html
+#[derive(Debug)]
+pub struct PadSrcRef<'a> {
+ strong: PadSrcStrong,
+ phantom: PhantomData<&'a PadSrcStrong>,
+}
+
+impl<'a> PadSrcRef<'a> {
+ fn new(inner_arc: Arc<PadSrcInner>) -> Self {
+ PadSrcRef {
+ strong: PadSrcStrong(inner_arc),
+ phantom: PhantomData,
+ }
+ }
+
+ pub fn gst_pad(&self) -> &gst::Pad {
+ self.strong.gst_pad()
+ }
+
+ ///// Spawns `future` using current [`PadContext`].
+ /////
+ ///// # Panics
+ /////
+ ///// This function panics if the `PadSrc` is not prepared.
+ /////
+ ///// [`PadContext`]: ../struct.PadContext.html
+ //pub fn spawn<Fut>(&self, future: Fut) -> JoinHandle<Fut::Output>
+ //where
+ // Fut: Future + Send + 'static,
+ // Fut::Output: Send + 'static,
+ //{
+ // self.strong.spawn(future)
+ //}
+
+ pub fn downgrade(&self) -> PadSrcWeak {
+ self.strong.downgrade()
+ }
+
+ pub async fn push(&self, buffer: gst::Buffer) -> Result<FlowSuccess, FlowError> {
+ self.strong.push(buffer).await
+ }
+
+ pub async fn push_list(&self, list: gst::BufferList) -> Result<FlowSuccess, FlowError> {
+ self.strong.push_list(list).await
+ }
+
+ pub async fn push_event(&self, event: gst::Event) -> bool {
+ self.strong.push_event(event).await
+ }
+
+ fn activate_mode_hook(
+ &self,
+ mode: gst::PadMode,
+ active: bool,
+ ) -> Result<(), gst::LoggableError> {
+ // Important: don't panic here as the hook is used without `catch_panic_pad_function`
+ // in the default `activatemode` handling
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "ActivateMode {:?}, {}", mode, active);
+
+ if mode == gst::PadMode::Pull {
+ gst_error!(RUNTIME_CAT, obj: self.gst_pad(), "Pull mode not supported by PadSrc");
+ return Err(gst_loggable_error!(
+ RUNTIME_CAT,
+ "Pull mode not supported by PadSrc"
+ ));
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+struct PadSrcStrong(Arc<PadSrcInner>);
+
+impl PadSrcStrong {
+ fn new(gst_pad: gst::Pad) -> Self {
+ PadSrcStrong(Arc::new(PadSrcInner::new(gst_pad)))
+ }
+
+ #[inline]
+ fn gst_pad(&self) -> &gst::Pad {
+ &self.0.gst_pad
+ }
+
+ //#[inline]
+ //fn spawn<Fut>(&self, future: Fut) -> JoinHandle<Fut::Output>
+ //where
+ // Fut: Future + Send + 'static,
+ // Fut::Output: Send + 'static,
+ //{
+ // let pad_ctx = self.pad_context_priv();
+ // pad_ctx
+ // .as_ref()
+ // .expect("PadContext not initialized")
+ // .spawn(future)
+ //}
+
+ #[inline]
+ fn downgrade(&self) -> PadSrcWeak {
+ PadSrcWeak(Arc::downgrade(&self.0))
+ }
+
+ #[inline]
+ async fn push(&self, buffer: gst::Buffer) -> Result<FlowSuccess, FlowError> {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Pushing {:?}", buffer);
+
+ let success = self.gst_pad().push(buffer).map_err(|err| {
+ gst_error!(RUNTIME_CAT,
+ obj: self.gst_pad(),
+ "Failed to push Buffer to PadSrc: {:?}",
+ err,
+ );
+ err
+ })?;
+
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Processing any pending sub tasks");
+ while Context::current_has_sub_tasks() {
+ Context::drain_sub_tasks().await?;
+ }
+
+ Ok(success)
+ }
+
+ #[inline]
+ async fn push_list(&self, list: gst::BufferList) -> Result<FlowSuccess, FlowError> {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Pushing {:?}", list);
+
+ let success = self.gst_pad().push_list(list).map_err(|err| {
+ gst_error!(
+ RUNTIME_CAT,
+ obj: self.gst_pad(),
+ "Failed to push BufferList to PadSrc: {:?}",
+ err,
+ );
+ err
+ })?;
+
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Processing any pending sub tasks");
+ while Context::current_has_sub_tasks() {
+ Context::drain_sub_tasks().await?;
+ }
+
+ Ok(success)
+ }
+
+ #[inline]
+ async fn push_event(&self, event: gst::Event) -> bool {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Pushing {:?}", event);
+
+ let was_handled = self.gst_pad().push_event(event);
+
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Processing any pending sub tasks");
+ while Context::current_has_sub_tasks() {
+ if Context::drain_sub_tasks().await.is_err() {
+ return false;
+ }
+ }
+
+ was_handled
+ }
+}
+
+/// The `PadSrc` which `Element`s must own.
+///
+/// Call [`downgrade`] if you need to `clone` the `PadSrc`.
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`downgrade`]: struct.PadSrc.html#method.downgrade
+/// [`pad` module]: index.html
+#[derive(Debug)]
+pub struct PadSrc(PadSrcStrong);
+
+impl PadSrc {
+ pub fn new(gst_pad: gst::Pad) -> Self {
+ let this = PadSrc(PadSrcStrong::new(gst_pad));
+ this.set_default_activatemode_function();
+
+ this
+ }
+
+ pub fn as_ref(&self) -> PadSrcRef<'_> {
+ PadSrcRef::new(Arc::clone(&(self.0).0))
+ }
+
+ pub fn gst_pad(&self) -> &gst::Pad {
+ self.0.gst_pad()
+ }
+
+ pub fn downgrade(&self) -> PadSrcWeak {
+ self.0.downgrade()
+ }
+
+ pub fn check_reconfigure(&self) -> bool {
+ self.gst_pad().check_reconfigure()
+ }
+
+ fn set_default_activatemode_function(&self) {
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activatemode_function(move |gst_pad, _parent, mode, active| {
+ // Important: don't panic here as we operate without `catch_panic_pad_function`
+ // because we may not know which element the PadSrc is associated to yet
+ this_weak
+ .upgrade()
+ .ok_or_else(|| {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "PadSrc no longer exists");
+ gst_loggable_error!(RUNTIME_CAT, "PadSrc no longer exists")
+ })?
+ .activate_mode_hook(mode, active)
+ });
+ }
+
+ ///// Spawns `future` using current [`PadContext`].
+ /////
+ ///// # Panics
+ /////
+ ///// This function panics if the `PadSrc` is not prepared.
+ /////
+ ///// [`PadContext`]: ../struct.PadContext.html
+ //pub fn spawn<Fut>(&self, future: Fut) -> JoinHandle<Fut::Output>
+ //where
+ // Fut: Future + Send + 'static,
+ // Fut::Output: Send + 'static,
+ //{
+ // self.0.spawn(future)
+ //}
+
+ fn init_pad_functions<H: PadSrcHandler>(&self, handler: &H) {
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activate_function(move |gst_pad, parent| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSrc activate");
+ Err(gst_loggable_error!(RUNTIME_CAT, "Panic in PadSrc activate"))
+ },
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSrc no longer exists");
+ handler.src_activate(&this_ref, imp, element)
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activatemode_function(move |gst_pad, parent, mode, active| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSrc activatemode");
+ Err(gst_loggable_error!(
+ RUNTIME_CAT,
+ "Panic in PadSrc activatemode"
+ ))
+ },
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSrc no longer exists");
+ this_ref.activate_mode_hook(mode, active)?;
+ handler.src_activatemode(&this_ref, imp, element, mode, active)
+ },
+ )
+ });
+
+ // No need to `set_event_function` since `set_event_full_function`
+ // overrides it and dispatches to `src_event` when necessary
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_event_full_function(move |_gst_pad, parent, event| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || Err(FlowError::Error),
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSrc no longer exists");
+ handler.src_event_full(&this_ref, imp, &element, event)
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_query_function(move |_gst_pad, parent, query| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || false,
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSrc no longer exists");
+ if !query.is_serialized() {
+ handler.src_query(&this_ref, imp, &element, query)
+ } else {
+ gst_fixme!(RUNTIME_CAT, obj: this_ref.gst_pad(), "Serialized Query not supported");
+ false
+ }
+ },
+ )
+ });
+ }
+
+ pub fn prepare<H: PadSrcHandler>(&self, handler: &H) {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Preparing");
+
+ self.init_pad_functions(handler);
+ }
+
+ /// Releases the resources held by this `PadSrc`.
+ pub fn unprepare(&self) {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Unpreparing");
+
+ self.gst_pad()
+ .set_activate_function(move |_gst_pad, _parent| {
+ Err(gst_loggable_error!(RUNTIME_CAT, "PadSrc unprepared"))
+ });
+ self.set_default_activatemode_function();
+ self.gst_pad()
+ .set_event_function(move |_gst_pad, _parent, _event| false);
+ self.gst_pad()
+ .set_event_full_function(move |_gst_pad, _parent, _event| Err(FlowError::Flushing));
+ self.gst_pad()
+ .set_query_function(move |_gst_pad, _parent, _query| false);
+ }
+
+ pub async fn push(&self, buffer: gst::Buffer) -> Result<FlowSuccess, FlowError> {
+ self.0.push(buffer).await
+ }
+
+ pub async fn push_list(&self, list: gst::BufferList) -> Result<FlowSuccess, FlowError> {
+ self.0.push_list(list).await
+ }
+
+ pub async fn push_event(&self, event: gst::Event) -> bool {
+ self.0.push_event(event).await
+ }
+}
+
+/// A trait to define `handler`s for [`PadSink`] callbacks.
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSink`]: struct.PadSink.html
+/// [`pad` module]: index.html
+pub trait PadSinkHandler: Clone + Send + Sync + 'static {
+ type ElementImpl: ElementImpl + ObjectSubclass;
+
+ fn sink_activate(
+ &self,
+ pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ ) -> Result<(), gst::LoggableError> {
+ let gst_pad = pad.gst_pad();
+ if gst_pad.is_active() {
+ gst_debug!(
+ RUNTIME_CAT,
+ obj: gst_pad,
+ "Already activated in {:?} mode ",
+ gst_pad.get_mode()
+ );
+ return Ok(());
+ }
+
+ gst_pad
+ .activate_mode(gst::PadMode::Push, true)
+ .map_err(|err| {
+ gst_error!(
+ RUNTIME_CAT,
+ obj: gst_pad,
+ "Error in PadSink activate: {:?}",
+ err
+ );
+ gst_loggable_error!(RUNTIME_CAT, "Error in PadSink activate: {:?}", err)
+ })
+ }
+
+ fn sink_activatemode(
+ &self,
+ _pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ _mode: gst::PadMode,
+ _active: bool,
+ ) -> Result<(), gst::LoggableError> {
+ Ok(())
+ }
+
+ fn sink_chain(
+ &self,
+ _pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ _buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<FlowSuccess, FlowError>> {
+ future::err(FlowError::NotSupported).boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ _pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ _element: &gst::Element,
+ _buffer_list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<FlowSuccess, FlowError>> {
+ future::err(FlowError::NotSupported).boxed()
+ }
+
+ fn sink_event(
+ &self,
+ pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ assert!(!event.is_serialized());
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+ pad.gst_pad().event_default(Some(element), event)
+ }
+
+ fn sink_event_serialized(
+ &self,
+ pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ assert!(event.is_serialized());
+ let pad_weak = pad.downgrade();
+ let element = element.clone();
+
+ async move {
+ let pad = pad_weak.upgrade().expect("PadSink no longer exists");
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ pad.gst_pad().event_default(Some(&element), event)
+ }
+ .boxed()
+ }
+
+ fn sink_event_full(
+ &self,
+ pad: &PadSinkRef,
+ imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> Result<FlowSuccess, FlowError> {
+ assert!(!event.is_serialized());
+ // default is to dispatch to `sink_event`
+ // (as implemented in `gst_pad_send_event_unchecked`)
+ let event_type = event.get_type();
+ event_to_event_full(self.sink_event(pad, imp, element, event), event_type)
+ }
+
+ fn sink_event_full_serialized(
+ &self,
+ pad: &PadSinkRef,
+ imp: &Self::ElementImpl,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, Result<FlowSuccess, FlowError>> {
+ assert!(event.is_serialized());
+ // default is to dispatch to `sink_event`
+ // (as implemented in `gst_pad_send_event_unchecked`)
+ let event_type = event.get_type();
+ event_to_event_full_serialized(
+ self.sink_event_serialized(pad, imp, element, event),
+ event_type,
+ )
+ }
+
+ fn sink_query(
+ &self,
+ pad: &PadSinkRef,
+ _imp: &Self::ElementImpl,
+ element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ if query.is_serialized() {
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Dropping {:?}", query);
+ // FIXME serialized queries should be handled with the dataflow
+ // but we can't return a `Future` because we couldn't honor QueryRef's lifetime
+ false
+ } else {
+ gst_log!(RUNTIME_CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+ pad.gst_pad().query_default(Some(element), query)
+ }
+ }
+}
+
+#[derive(Debug)]
+struct PadSinkInner {
+ gst_pad: gst::Pad,
+}
+
+impl PadSinkInner {
+ fn new(gst_pad: gst::Pad) -> Self {
+ if gst_pad.get_direction() != gst::PadDirection::Sink {
+ panic!("Wrong pad direction for PadSink");
+ }
+
+ PadSinkInner { gst_pad }
+ }
+}
+
+/// A [`PadSink`] which can be moved in `Handler`s functions and `Future`s.
+///
+/// Call [`upgrade`] to use the [`PadSink`].
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSink`]: struct.PadSink.html
+/// [`upgrade`]: struct.PadSinkWeak.html#method.upgrade
+/// [`pad` module]: index.html
+#[derive(Clone, Debug)]
+pub struct PadSinkWeak(Weak<PadSinkInner>);
+
+impl PadSinkWeak {
+ pub fn upgrade(&self) -> Option<PadSinkRef<'_>> {
+ self.0.upgrade().map(PadSinkRef::new)
+ }
+}
+
+/// A [`PadSink`] to be used in [`handler`]s functions and `Future`s.
+///
+/// Call [`downgrade`] if you need to `clone` the [`PadSink`].
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`PadSink`]: struct.PadSink.html
+/// [`handler`]: trait.PadSinkHandler.html
+/// [`downgrade`]: struct.PadSinkRef.html#method.downgrade
+/// [`pad` module]: index.html
+#[derive(Debug)]
+pub struct PadSinkRef<'a> {
+ strong: PadSinkStrong,
+ phantom: PhantomData<&'a PadSrcStrong>,
+}
+
+impl<'a> PadSinkRef<'a> {
+ fn new(inner_arc: Arc<PadSinkInner>) -> Self {
+ PadSinkRef {
+ strong: PadSinkStrong(inner_arc),
+ phantom: PhantomData,
+ }
+ }
+
+ pub fn gst_pad(&self) -> &gst::Pad {
+ self.strong.gst_pad()
+ }
+
+ pub fn downgrade(&self) -> PadSinkWeak {
+ self.strong.downgrade()
+ }
+
+ fn activate_mode_hook(
+ &self,
+ mode: gst::PadMode,
+ active: bool,
+ ) -> Result<(), gst::LoggableError> {
+ // Important: don't panic here as the hook is used without `catch_panic_pad_function`
+ // in the default `activatemode` handling
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "ActivateMode {:?}, {}", mode, active);
+
+ if mode == gst::PadMode::Pull {
+ gst_error!(RUNTIME_CAT, obj: self.gst_pad(), "Pull mode not supported by PadSink");
+ return Err(gst_loggable_error!(
+ RUNTIME_CAT,
+ "Pull mode not supported by PadSink"
+ ));
+ }
+
+ Ok(())
+ }
+
+ fn handle_future(
+ &self,
+ fut: impl Future<Output = Result<FlowSuccess, FlowError>> + Send + 'static,
+ ) -> Result<FlowSuccess, FlowError> {
+ // First try to add it as a sub task to the current task, if any
+ if let Err(fut) = Context::add_sub_task(fut.map(|res| res.map(drop))) {
+ // FIXME: update comments below
+ // Not on a context thread: execute the Future immediately.
+ //
+ // - If there is no PadContext, we don't have any other options.
+ // - If there is a PadContext, it means that we received it from
+ // an upstream element, but there is at least one non-ts element
+ // operating on another thread in between, so we can't take
+ // advantage of the task queue.
+ //
+ // Note: we don't use `crate::runtime::executor::block_on` here
+ // because `Context::is_context_thread()` is checked in the `if`
+ // statement above.
+ block_on_or_add_sub_task(fut.map(|res| res.map(|_| gst::FlowSuccess::Ok)))
+ .unwrap_or(Ok(gst::FlowSuccess::Ok))
+ } else {
+ Ok(gst::FlowSuccess::Ok)
+ }
+ }
+}
+
+#[derive(Debug)]
+struct PadSinkStrong(Arc<PadSinkInner>);
+
+impl PadSinkStrong {
+ fn new(gst_pad: gst::Pad) -> Self {
+ PadSinkStrong(Arc::new(PadSinkInner::new(gst_pad)))
+ }
+
+ fn gst_pad(&self) -> &gst::Pad {
+ &self.0.gst_pad
+ }
+
+ fn downgrade(&self) -> PadSinkWeak {
+ PadSinkWeak(Arc::downgrade(&self.0))
+ }
+}
+
+/// The `PadSink` which `Element`s must own.
+///
+/// Call [`downgrade`] if you need to `clone` the `PadSink`.
+///
+/// *See the [`pad` module] documentation for a description of the model.*
+///
+/// [`downgrade`]: struct.PadSink.html#method.downgrade
+/// [`pad` module]: index.html
+#[derive(Debug)]
+pub struct PadSink(PadSinkStrong);
+
+impl PadSink {
+ pub fn new(gst_pad: gst::Pad) -> Self {
+ let this = PadSink(PadSinkStrong::new(gst_pad));
+ this.set_default_activatemode_function();
+
+ this
+ }
+
+ pub fn as_ref(&self) -> PadSinkRef<'_> {
+ PadSinkRef::new(Arc::clone(&(self.0).0))
+ }
+
+ pub fn gst_pad(&self) -> &gst::Pad {
+ self.0.gst_pad()
+ }
+
+ pub fn downgrade(&self) -> PadSinkWeak {
+ self.0.downgrade()
+ }
+
+ fn set_default_activatemode_function(&self) {
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activatemode_function(move |gst_pad, _parent, mode, active| {
+ // Important: don't panic here as we operate without `catch_panic_pad_function`
+ // because we may not know which element the PadSrc is associated to yet
+ this_weak
+ .upgrade()
+ .ok_or_else(|| {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "PadSink no longer exists");
+ gst_loggable_error!(RUNTIME_CAT, "PadSink no longer exists")
+ })?
+ .activate_mode_hook(mode, active)
+ });
+ }
+
+ fn init_pad_functions<H: PadSinkHandler>(&self, handler: &H) {
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activate_function(move |gst_pad, parent| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSink activate");
+ Err(gst_loggable_error!(
+ RUNTIME_CAT,
+ "Panic in PadSink activate"
+ ))
+ },
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ handler.sink_activate(&this_ref, imp, element)
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_activatemode_function(move |gst_pad, parent, mode, active| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || {
+ gst_error!(RUNTIME_CAT, obj: gst_pad, "Panic in PadSink activatemode");
+ Err(gst_loggable_error!(
+ RUNTIME_CAT,
+ "Panic in PadSink activatemode"
+ ))
+ },
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ this_ref.activate_mode_hook(mode, active)?;
+
+ handler.sink_activatemode(&this_ref, imp, element, mode, active)
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_chain_function(move |_gst_pad, parent, buffer| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || Err(FlowError::Error),
+ move |imp, element| {
+ if Context::current_has_sub_tasks() {
+ let this_weak = this_weak.clone();
+ let handler = handler.clone();
+ let element = element.clone();
+ let delayed_fut = async move {
+ let imp =
+ <H::ElementImpl as ObjectSubclass>::from_instance(&element);
+ let this_ref =
+ this_weak.upgrade().ok_or(gst::FlowError::Flushing)?;
+ handler.sink_chain(&this_ref, imp, &element, buffer).await
+ };
+ let _ = Context::add_sub_task(delayed_fut.map(|res| res.map(drop)));
+
+ Ok(gst::FlowSuccess::Ok)
+ } else {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ let chain_fut = handler.sink_chain(&this_ref, imp, &element, buffer);
+ this_ref.handle_future(chain_fut)
+ }
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_chain_list_function(move |_gst_pad, parent, list| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || Err(FlowError::Error),
+ move |imp, element| {
+ if Context::current_has_sub_tasks() {
+ let this_weak = this_weak.clone();
+ let handler = handler.clone();
+ let element = element.clone();
+ let delayed_fut = async move {
+ let imp =
+ <H::ElementImpl as ObjectSubclass>::from_instance(&element);
+ let this_ref =
+ this_weak.upgrade().ok_or(gst::FlowError::Flushing)?;
+ handler
+ .sink_chain_list(&this_ref, imp, &element, list)
+ .await
+ };
+ let _ = Context::add_sub_task(delayed_fut.map(|res| res.map(drop)));
+
+ Ok(gst::FlowSuccess::Ok)
+ } else {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ let chain_list_fut =
+ handler.sink_chain_list(&this_ref, imp, &element, list);
+ this_ref.handle_future(chain_list_fut)
+ }
+ },
+ )
+ });
+
+ // No need to `set_event_function` since `set_event_full_function`
+ // overrides it and dispatches to `sink_event` when necessary
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_event_full_function(move |_gst_pad, parent, event| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || Err(FlowError::Error),
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ if event.is_serialized() {
+ if Context::current_has_sub_tasks() {
+ let this_weak = this_weak.clone();
+ let handler = handler.clone();
+ let element = element.clone();
+ let delayed_fut = async move {
+ let imp =
+ <H::ElementImpl as ObjectSubclass>::from_instance(&element);
+ let this_ref =
+ this_weak.upgrade().ok_or(gst::FlowError::Flushing)?;
+
+ handler
+ .sink_event_full_serialized(&this_ref, imp, &element, event)
+ .await
+ };
+ let _ = Context::add_sub_task(delayed_fut.map(|res| res.map(drop)));
+
+ Ok(gst::FlowSuccess::Ok)
+ } else {
+ let event_fut = handler
+ .sink_event_full_serialized(&this_ref, imp, &element, event);
+ this_ref.handle_future(event_fut)
+ }
+ } else {
+ handler.sink_event_full(&this_ref, imp, &element, event)
+ }
+ },
+ )
+ });
+
+ let handler_clone = handler.clone();
+ let this_weak = self.downgrade();
+ self.gst_pad()
+ .set_query_function(move |_gst_pad, parent, query| {
+ let handler = handler_clone.clone();
+ let this_weak = this_weak.clone();
+ H::ElementImpl::catch_panic_pad_function(
+ parent,
+ || false,
+ move |imp, element| {
+ let this_ref = this_weak.upgrade().expect("PadSink no longer exists");
+ if !query.is_serialized() {
+ handler.sink_query(&this_ref, imp, &element, query)
+ } else {
+ gst_fixme!(RUNTIME_CAT, obj: this_ref.gst_pad(), "Serialized Query not supported");
+ false
+ }
+ },
+ )
+ });
+ }
+
+ pub fn prepare<H: PadSinkHandler>(&self, handler: &H) {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Preparing");
+ self.init_pad_functions(handler);
+ }
+
+ /// Releases the resources held by this `PadSink`.
+ pub fn unprepare(&self) {
+ gst_log!(RUNTIME_CAT, obj: self.gst_pad(), "Unpreparing");
+
+ self.gst_pad()
+ .set_activate_function(move |_gst_pad, _parent| {
+ Err(gst_loggable_error!(RUNTIME_CAT, "PadSink unprepared"))
+ });
+ self.set_default_activatemode_function();
+ self.gst_pad()
+ .set_chain_function(move |_gst_pad, _parent, _buffer| Err(FlowError::Flushing));
+ self.gst_pad()
+ .set_chain_list_function(move |_gst_pad, _parent, _list| Err(FlowError::Flushing));
+ self.gst_pad()
+ .set_event_function(move |_gst_pad, _parent, _event| false);
+ self.gst_pad()
+ .set_event_full_function(move |_gst_pad, _parent, _event| Err(FlowError::Flushing));
+ self.gst_pad()
+ .set_query_function(move |_gst_pad, _parent, _query| false);
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/runtime/task.rs b/generic/gst-plugin-threadshare/src/runtime/task.rs
new file mode 100644
index 000000000..c48327cce
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/runtime/task.rs
@@ -0,0 +1,725 @@
+// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
+// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! An execution loop to run asynchronous processing.
+
+use futures::channel::oneshot;
+use futures::future::{abortable, AbortHandle, Aborted};
+use futures::prelude::*;
+
+use gst::{gst_debug, gst_error, gst_log, gst_trace, gst_warning};
+
+use std::fmt;
+use std::sync::{Arc, Mutex};
+
+use super::executor::{block_on, yield_now};
+use super::{Context, JoinHandle, RUNTIME_CAT};
+
+#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
+pub enum TaskState {
+ Cancelled,
+ Started,
+ Stopped,
+ Paused,
+ Pausing,
+ Preparing,
+ Unprepared,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum TaskError {
+ ActiveTask,
+}
+
+impl fmt::Display for TaskError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ TaskError::ActiveTask => write!(f, "The task is still active"),
+ }
+ }
+}
+
+impl std::error::Error for TaskError {}
+
+#[derive(Debug)]
+struct TaskInner {
+ context: Option<Context>,
+ state: TaskState,
+ prepare_handle: Option<JoinHandle<Result<Result<(), gst::FlowError>, Aborted>>>,
+ prepare_abort_handle: Option<AbortHandle>,
+ abort_handle: Option<AbortHandle>,
+ loop_handle: Option<JoinHandle<Result<(), Aborted>>>,
+ resume_sender: Option<oneshot::Sender<()>>,
+}
+
+impl Default for TaskInner {
+ fn default() -> Self {
+ TaskInner {
+ context: None,
+ state: TaskState::Unprepared,
+ prepare_handle: None,
+ prepare_abort_handle: None,
+ abort_handle: None,
+ loop_handle: None,
+ resume_sender: None,
+ }
+ }
+}
+
+impl Drop for TaskInner {
+ fn drop(&mut self) {
+ if self.state != TaskState::Unprepared {
+ panic!("Missing call to `Task::unprepared`");
+ }
+ }
+}
+
+/// A `Task` operating on a `threadshare` [`Context`].
+///
+/// [`Context`]: ../executor/struct.Context.html
+#[derive(Debug)]
+pub struct Task(Arc<Mutex<TaskInner>>);
+
+impl Default for Task {
+ fn default() -> Self {
+ Task(Arc::new(Mutex::new(TaskInner::default())))
+ }
+}
+
+impl Task {
+ pub fn prepare_with_func<F, Fut>(
+ &self,
+ context: Context,
+ prepare_func: F,
+ ) -> Result<(), TaskError>
+ where
+ F: (FnOnce() -> Fut) + Send + 'static,
+ Fut: Future<Output = ()> + Send + 'static,
+ {
+ gst_debug!(RUNTIME_CAT, "Preparing task");
+
+ let mut inner = self.0.lock().unwrap();
+ if inner.state != TaskState::Unprepared {
+ return Err(TaskError::ActiveTask);
+ }
+
+ // Spawn prepare function in the background
+ let task_weak = Arc::downgrade(&self.0);
+ let (prepare_fut, prepare_abort_handle) = abortable(async move {
+ gst_trace!(RUNTIME_CAT, "Calling task prepare function");
+
+ prepare_func().await;
+
+ gst_trace!(RUNTIME_CAT, "Task prepare function finished");
+
+ while Context::current_has_sub_tasks() {
+ Context::drain_sub_tasks().await?;
+ }
+
+ // Once the prepare function is finished we can forget the corresponding
+ // handles so that unprepare and friends don't have to block on it anymore
+ if let Some(task_inner) = task_weak.upgrade() {
+ let mut inner = task_inner.lock().unwrap();
+ inner.prepare_abort_handle = None;
+ inner.prepare_handle = None;
+ }
+
+ gst_trace!(RUNTIME_CAT, "Task fully prepared");
+
+ Ok(())
+ });
+ let prepare_handle = context.spawn(prepare_fut);
+ inner.prepare_handle = Some(prepare_handle);
+ inner.prepare_abort_handle = Some(prepare_abort_handle);
+
+ inner.context = Some(context);
+
+ inner.state = TaskState::Preparing;
+ gst_debug!(RUNTIME_CAT, "Task prepared");
+
+ Ok(())
+ }
+
+ pub fn prepare(&self, context: Context) -> Result<(), TaskError> {
+ gst_debug!(RUNTIME_CAT, "Preparing task");
+
+ let mut inner = self.0.lock().unwrap();
+ if inner.state != TaskState::Unprepared {
+ return Err(TaskError::ActiveTask);
+ }
+
+ inner.prepare_handle = None;
+ inner.prepare_abort_handle = None;
+
+ inner.context = Some(context);
+
+ inner.state = TaskState::Stopped;
+ gst_debug!(RUNTIME_CAT, "Task prepared");
+
+ Ok(())
+ }
+
+ pub fn unprepare(&self) -> Result<(), TaskError> {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state != TaskState::Stopped {
+ gst_error!(
+ RUNTIME_CAT,
+ "Attempt to Unprepare a task in state {:?}",
+ inner.state
+ );
+ return Err(TaskError::ActiveTask);
+ }
+
+ gst_debug!(RUNTIME_CAT, "Unpreparing task");
+
+ // Abort any pending preparation
+ if let Some(abort_handle) = inner.prepare_abort_handle.take() {
+ abort_handle.abort();
+ }
+ let prepare_handle = inner.prepare_handle.take();
+
+ let context = inner.context.take().unwrap();
+
+ inner.state = TaskState::Unprepared;
+
+ drop(inner);
+
+ if let Some(prepare_handle) = prepare_handle {
+ if let Some((cur_context, cur_task_id)) = Context::current_task() {
+ if prepare_handle.is_current() {
+ // This would deadlock!
+ gst_warning!(
+ RUNTIME_CAT,
+ "Trying to stop task {:?} from itself, not waiting",
+ prepare_handle
+ );
+ } else if cur_context == context {
+ // This is ok: as we're on the same thread and the prepare function is aborted
+ // this means that it won't ever be called, and we're not inside it here
+ gst_debug!(
+ RUNTIME_CAT,
+ "Asynchronously waiting for task {:?} on the same context",
+ prepare_handle
+ );
+
+ let _ = Context::add_sub_task(async move {
+ let _ = prepare_handle.await;
+ Ok(())
+ });
+ } else {
+ // This is suboptimal but we can't really do this asynchronously as otherwise
+ // it might be started again before it's actually stopped.
+ gst_warning!(
+ RUNTIME_CAT,
+ "Synchronously waiting for task {:?} on task {:?} on context {}",
+ prepare_handle,
+ cur_task_id,
+ cur_context.name()
+ );
+ let _ = block_on(prepare_handle);
+ }
+ } else {
+ gst_debug!(
+ RUNTIME_CAT,
+ "Synchronously waiting for task {:?}",
+ prepare_handle
+ );
+ let _ = block_on(prepare_handle);
+ }
+ }
+
+ gst_debug!(RUNTIME_CAT, "Task unprepared");
+
+ Ok(())
+ }
+
+ pub fn state(&self) -> TaskState {
+ self.0.lock().unwrap().state
+ }
+
+ pub fn context(&self) -> Option<Context> {
+ self.0.lock().unwrap().context.as_ref().cloned()
+ }
+
+ /// `Starts` the `Task`.
+ ///
+ /// The `Task` will loop on the provided @func.
+ /// The execution occurs on the `Task`'s context.
+ pub fn start<F, Fut>(&self, mut func: F)
+ where
+ F: (FnMut() -> Fut) + Send + 'static,
+ Fut: Future<Output = glib::Continue> + Send + 'static,
+ {
+ let inner_clone = Arc::clone(&self.0);
+ let mut inner = self.0.lock().unwrap();
+ match inner.state {
+ TaskState::Started => {
+ gst_log!(RUNTIME_CAT, "Task already Started");
+ return;
+ }
+ TaskState::Pausing => {
+ gst_debug!(RUNTIME_CAT, "Re-starting a Pausing task");
+
+ assert!(inner.resume_sender.is_none());
+
+ inner.state = TaskState::Started;
+ return;
+ }
+ TaskState::Paused => {
+ inner
+ .resume_sender
+ .take()
+ .expect("Task Paused but the resume_sender is already taken")
+ .send(())
+ .expect("Task Paused but the resume_receiver was dropped");
+
+ gst_log!(RUNTIME_CAT, "Resume requested");
+ return;
+ }
+ TaskState::Stopped | TaskState::Cancelled | TaskState::Preparing => (),
+ TaskState::Unprepared => panic!("Attempt to start an unprepared Task"),
+ }
+
+ gst_debug!(RUNTIME_CAT, "Starting Task");
+
+ let prepare_handle = inner.prepare_handle.take();
+
+ // If the task was only cancelled and not actually stopped yet then
+ // wait for that to happen as first thing in the new task.
+ let loop_handle = inner.loop_handle.take();
+
+ let (loop_fut, abort_handle) = abortable(async move {
+ let task_id = Context::current_task().unwrap().1;
+
+ if let Some(loop_handle) = loop_handle {
+ gst_trace!(
+ RUNTIME_CAT,
+ "Waiting for previous loop to finish before starting"
+ );
+ let _ = loop_handle.await;
+ }
+
+ // First await on the prepare function, if any
+ if let Some(prepare_handle) = prepare_handle {
+ gst_trace!(RUNTIME_CAT, "Waiting for prepare before starting");
+ let res = prepare_handle.await;
+ if res.is_err() {
+ gst_warning!(RUNTIME_CAT, "Preparing failed");
+ inner_clone.lock().unwrap().state = TaskState::Unprepared;
+
+ return;
+ }
+
+ inner_clone.lock().unwrap().state = TaskState::Stopped;
+ }
+
+ gst_trace!(RUNTIME_CAT, "Starting task loop");
+
+ // Then loop as long as we're actually running
+ loop {
+ let mut resume_receiver = {
+ let mut inner = inner_clone.lock().unwrap();
+ match inner.state {
+ TaskState::Started => None,
+ TaskState::Pausing => {
+ let (sender, receiver) = oneshot::channel();
+ inner.resume_sender = Some(sender);
+
+ inner.state = TaskState::Paused;
+
+ Some(receiver)
+ }
+ TaskState::Stopped | TaskState::Cancelled => {
+ gst_trace!(RUNTIME_CAT, "Stopping task loop");
+ break;
+ }
+ TaskState::Paused => {
+ unreachable!("The Paused state is controlled by the loop");
+ }
+ other => {
+ unreachable!("Task loop iteration in state {:?}", other);
+ }
+ }
+ };
+
+ if let Some(resume_receiver) = resume_receiver.take() {
+ gst_trace!(RUNTIME_CAT, "Task loop paused");
+
+ match resume_receiver.await {
+ Ok(_) => {
+ gst_trace!(RUNTIME_CAT, "Resuming task loop");
+ inner_clone.lock().unwrap().state = TaskState::Started;
+ }
+ Err(_) => {
+ gst_trace!(RUNTIME_CAT, "Resume cancelled");
+ break;
+ }
+ }
+ }
+
+ if func().await == glib::Continue(false) {
+ let mut inner = inner_clone.lock().unwrap();
+
+ // Make sure to only reset the state if this is still the correct task
+ // and no new task was started in the meantime
+ if inner.state == TaskState::Started
+ && inner
+ .loop_handle
+ .as_ref()
+ .map(|h| h.task_id() == task_id)
+ .unwrap_or(false)
+ {
+ gst_trace!(RUNTIME_CAT, "Exiting task loop");
+ inner.state = TaskState::Cancelled;
+ }
+
+ break;
+ }
+
+ // Make sure the loop can be aborted even if `func` never goes `Pending`.
+ yield_now().await;
+ }
+
+ // Once the loop function is finished we can forget the corresponding
+ // handles so that unprepare and friends don't have to block on it anymore
+ {
+ let mut inner = inner_clone.lock().unwrap();
+
+ // Make sure to only reset the state if this is still the correct task
+ // and no new task was started in the meantime
+ if inner
+ .loop_handle
+ .as_ref()
+ .map(|h| h.task_id() == task_id)
+ .unwrap_or(false)
+ {
+ inner.abort_handle = None;
+ inner.loop_handle = None;
+ inner.state = TaskState::Stopped;
+ }
+ }
+
+ gst_trace!(RUNTIME_CAT, "Task loop finished");
+ });
+
+ let loop_handle = inner
+ .context
+ .as_ref()
+ .expect("Context not set")
+ .spawn(loop_fut);
+
+ inner.abort_handle = Some(abort_handle);
+ inner.loop_handle = Some(loop_handle);
+ inner.state = TaskState::Started;
+
+ gst_debug!(RUNTIME_CAT, "Task Started");
+ }
+
+ /// Requests the `Task` loop to pause.
+ ///
+ /// If an iteration is in progress, it will run to completion,
+ /// then no more iteration will be executed before `start` is called again.
+ pub fn pause(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state != TaskState::Started {
+ gst_log!(RUNTIME_CAT, "Task not started");
+ return;
+ }
+
+ inner.state = TaskState::Pausing;
+ gst_debug!(RUNTIME_CAT, "Pause requested");
+ }
+
+ /// Cancels the `Task` so that it stops running as soon as possible.
+ pub fn cancel(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state != TaskState::Started
+ && inner.state != TaskState::Paused
+ && inner.state != TaskState::Pausing
+ {
+ gst_log!(RUNTIME_CAT, "Task not Started nor Paused");
+ return;
+ }
+
+ gst_debug!(RUNTIME_CAT, "Cancelling Task");
+
+ // Abort any still running loop function
+ if let Some(abort_handle) = inner.abort_handle.take() {
+ abort_handle.abort();
+ }
+
+ inner.resume_sender = None;
+
+ inner.state = TaskState::Cancelled;
+ }
+
+ /// Stops the `Started` `Task` and wait for it to finish.
+ pub fn stop(&self) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.state == TaskState::Stopped || inner.state == TaskState::Preparing {
+ gst_log!(RUNTIME_CAT, "Task loop already stopped");
+ return;
+ }
+
+ gst_debug!(RUNTIME_CAT, "Stopping Task");
+
+ inner.state = TaskState::Stopped;
+
+ // Abort any still running loop function
+ if let Some(abort_handle) = inner.abort_handle.take() {
+ abort_handle.abort();
+ }
+
+ // And now wait for it to actually stop
+ let loop_handle = inner.loop_handle.take();
+
+ inner.resume_sender = None;
+
+ let context = inner.context.as_ref().unwrap().clone();
+ drop(inner);
+
+ if let Some(loop_handle) = loop_handle {
+ if let Some((cur_context, cur_task_id)) = Context::current_task() {
+ if loop_handle.is_current() {
+ // This would deadlock!
+ gst_warning!(
+ RUNTIME_CAT,
+ "Trying to stop task {:?} from itself, not waiting",
+ loop_handle
+ );
+ } else if cur_context == context {
+ // This is ok: as we're on the same thread and the loop function is aborted
+ // this means that it won't ever be called, and we're not inside it here
+ gst_debug!(
+ RUNTIME_CAT,
+ "Asynchronously waiting for task {:?} on the same context",
+ loop_handle
+ );
+
+ let _ = Context::add_sub_task(async move {
+ let _ = loop_handle.await;
+ Ok(())
+ });
+ } else {
+ // This is suboptimal but we can't really do this asynchronously as otherwise
+ // it might be started again before it's actually stopped.
+ gst_warning!(
+ RUNTIME_CAT,
+ "Synchronously waiting for task {:?} on task {:?} on context {}",
+ loop_handle,
+ cur_task_id,
+ cur_context.name()
+ );
+ let _ = block_on(loop_handle);
+ }
+ } else {
+ gst_debug!(
+ RUNTIME_CAT,
+ "Synchronously waiting for task {:?}",
+ loop_handle
+ );
+ let _ = block_on(loop_handle);
+ }
+ }
+
+ gst_debug!(RUNTIME_CAT, "Task stopped");
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use futures::channel::{mpsc, oneshot};
+ use futures::lock::Mutex;
+
+ use std::sync::Arc;
+
+ use crate::runtime::Context;
+
+ use super::*;
+
+ #[tokio::test]
+ async fn task() {
+ gst::init().unwrap();
+
+ let context = Context::acquire("task", 2).unwrap();
+
+ let task = Task::default();
+ task.prepare(context).unwrap();
+
+ let (mut sender, receiver) = mpsc::channel(0);
+ let receiver = Arc::new(Mutex::new(receiver));
+
+ gst_debug!(RUNTIME_CAT, "task test: starting");
+ task.start(move || {
+ let receiver = Arc::clone(&receiver);
+ async move {
+ gst_debug!(RUNTIME_CAT, "task test: awaiting receiver");
+ match receiver.lock().await.next().await {
+ Some(_) => {
+ gst_debug!(RUNTIME_CAT, "task test: item received");
+ glib::Continue(true)
+ }
+ None => {
+ gst_debug!(RUNTIME_CAT, "task test: channel complete");
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+
+ gst_debug!(RUNTIME_CAT, "task test: sending item");
+ sender.send(()).await.unwrap();
+ gst_debug!(RUNTIME_CAT, "task test: item sent");
+
+ gst_debug!(RUNTIME_CAT, "task test: dropping sender");
+ drop(sender);
+
+ gst_debug!(RUNTIME_CAT, "task test: stopping");
+ task.stop();
+ gst_debug!(RUNTIME_CAT, "task test: stopped");
+
+ task.unprepare().unwrap();
+ gst_debug!(RUNTIME_CAT, "task test: unprepared");
+ }
+
+ #[tokio::test]
+ async fn task_with_prepare_func() {
+ gst::init().unwrap();
+
+ let context = Context::acquire("task_with_prepare_func", 2).unwrap();
+
+ let task = Task::default();
+
+ let (prepare_sender, prepare_receiver) = oneshot::channel();
+ task.prepare_with_func(context, move || async move {
+ prepare_sender.send(()).unwrap();
+ })
+ .unwrap();
+
+ let (mut sender, receiver) = mpsc::channel(0);
+ let receiver = Arc::new(Mutex::new(receiver));
+
+ let mut prepare_receiver = Some(prepare_receiver);
+
+ gst_debug!(RUNTIME_CAT, "task test: starting");
+ task.start(move || {
+ if let Some(mut prepare_receiver) = prepare_receiver.take() {
+ assert_eq!(prepare_receiver.try_recv().unwrap(), Some(()));
+ }
+
+ let receiver = Arc::clone(&receiver);
+ async move {
+ gst_debug!(RUNTIME_CAT, "task test: awaiting receiver");
+ match receiver.lock().await.next().await {
+ Some(_) => {
+ gst_debug!(RUNTIME_CAT, "task test: item received");
+ glib::Continue(true)
+ }
+ None => {
+ gst_debug!(RUNTIME_CAT, "task test: channel complete");
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+
+ gst_debug!(RUNTIME_CAT, "task test: sending item");
+ sender.send(()).await.unwrap();
+ gst_debug!(RUNTIME_CAT, "task test: item sent");
+
+ gst_debug!(RUNTIME_CAT, "task test: dropping sender");
+ drop(sender);
+
+ gst_debug!(RUNTIME_CAT, "task test: stopping");
+ task.stop();
+ gst_debug!(RUNTIME_CAT, "task test: stopped");
+
+ task.unprepare().unwrap();
+ gst_debug!(RUNTIME_CAT, "task test: unprepared");
+ }
+
+ #[tokio::test]
+ async fn pause_start() {
+ use gst::gst_error;
+
+ gst::init().unwrap();
+
+ let context = Context::acquire("task_pause_start", 2).unwrap();
+
+ let task = Task::default();
+ task.prepare(context).unwrap();
+
+ let (iter_sender, mut iter_receiver) = mpsc::channel(0);
+ let iter_sender = Arc::new(Mutex::new(iter_sender));
+
+ let (mut complete_sender, complete_receiver) = mpsc::channel(0);
+ let complete_receiver = Arc::new(Mutex::new(complete_receiver));
+
+ gst_debug!(RUNTIME_CAT, "task_pause_start: starting");
+ task.start(move || {
+ let iter_sender = Arc::clone(&iter_sender);
+ let complete_receiver = Arc::clone(&complete_receiver);
+ async move {
+ gst_debug!(RUNTIME_CAT, "task_pause_start: entering iteration");
+ iter_sender.lock().await.send(()).await.unwrap();
+
+ gst_debug!(
+ RUNTIME_CAT,
+ "task_pause_start: iteration awaiting completion"
+ );
+ complete_receiver.lock().await.next().await.unwrap();
+ gst_debug!(RUNTIME_CAT, "task_pause_start: iteration complete");
+ glib::Continue(true)
+ }
+ });
+
+ gst_debug!(RUNTIME_CAT, "task_pause_start: awaiting 1st iteration");
+ iter_receiver.next().await.unwrap();
+
+ task.pause();
+
+ gst_debug!(
+ RUNTIME_CAT,
+ "task_pause_start: sending 1st iteration completion"
+ );
+ complete_sender.send(()).await.unwrap();
+
+ // Loop held on
+ iter_receiver.try_next().unwrap_err();
+
+ task.start(|| {
+ gst_error!(
+ RUNTIME_CAT,
+ "task_pause_start: reached start to resume closure"
+ );
+ future::pending()
+ });
+
+ gst_debug!(RUNTIME_CAT, "task_pause_start: awaiting 2d iteration");
+ iter_receiver.next().await.unwrap();
+
+ gst_debug!(
+ RUNTIME_CAT,
+ "task_pause_start: sending 2d iteration completion"
+ );
+ complete_sender.send(()).await.unwrap();
+
+ task.stop();
+ task.unprepare().unwrap();
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/runtime/time.rs b/generic/gst-plugin-threadshare/src/runtime/time.rs
new file mode 100644
index 000000000..0ea857b0c
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/runtime/time.rs
@@ -0,0 +1,37 @@
+// Copyright (C) 2020 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+//! Wrappers for the underlying runtime specific time related Futures.
+
+use futures::prelude::*;
+use futures::stream::StreamExt;
+
+use std::time::Duration;
+
+/// Wait until the given `delay` has elapsed.
+///
+/// This must be called from within the target runtime environment.
+pub async fn delay_for(delay: Duration) {
+ tokio::time::delay_for(delay).map(drop).await;
+}
+
+/// Builds a `Stream` that yields at `interval.
+///
+/// This must be called from within the target runtime environment.
+pub fn interval(interval: Duration) -> impl Stream<Item = ()> {
+ tokio::time::interval(interval).map(drop)
+}
diff --git a/generic/gst-plugin-threadshare/src/socket.rs b/generic/gst-plugin-threadshare/src/socket.rs
new file mode 100644
index 000000000..790e977bc
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/socket.rs
@@ -0,0 +1,489 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+// Copyright (C) 2018 LEE Dongjun <redongjun@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::{abortable, AbortHandle, Aborted, BoxFuture};
+use futures::prelude::*;
+
+use gst;
+use gst::prelude::*;
+use gst::{gst_debug, gst_error, gst_error_msg};
+
+use lazy_static::lazy_static;
+
+use std::io;
+use std::sync::{Arc, Mutex};
+
+use gio;
+use gio::prelude::*;
+use gio_sys as gio_ffi;
+use gobject_sys as gobject_ffi;
+
+#[cfg(unix)]
+use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+
+#[cfg(windows)]
+use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
+
+lazy_static! {
+ static ref SOCKET_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-socket",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing Socket"),
+ );
+}
+
+pub struct Socket<T: SocketRead + 'static>(Arc<Mutex<SocketInner<T>>>);
+
+pub trait SocketRead: Send + Unpin {
+ const DO_TIMESTAMP: bool;
+
+ fn read<'buf>(
+ &self,
+ buffer: &'buf mut [u8],
+ ) -> BoxFuture<'buf, io::Result<(usize, Option<std::net::SocketAddr>)>>;
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum SocketState {
+ Paused,
+ Prepared,
+ Started,
+ Unprepared,
+}
+
+struct SocketInner<T: SocketRead + 'static> {
+ state: SocketState,
+ element: gst::Element,
+ buffer_pool: gst::BufferPool,
+ clock: Option<gst::Clock>,
+ base_time: Option<gst::ClockTime>,
+ create_read_handle: Option<AbortHandle>,
+ create_reader_fut: Option<BoxFuture<'static, Result<T, SocketError>>>,
+ read_handle: Option<AbortHandle>,
+ reader: Option<T>,
+}
+
+impl<T: SocketRead + 'static> Socket<T> {
+ pub fn new<F>(
+ element: &gst::Element,
+ buffer_pool: gst::BufferPool,
+ create_reader_fut: F,
+ ) -> Result<Self, ()>
+ where
+ F: Future<Output = Result<T, SocketError>> + Send + 'static,
+ {
+ let socket = Socket(Arc::new(Mutex::new(SocketInner::<T> {
+ state: SocketState::Unprepared,
+ element: element.clone(),
+ buffer_pool,
+ clock: None,
+ base_time: None,
+ create_read_handle: None,
+ create_reader_fut: Some(create_reader_fut.boxed()),
+ read_handle: None,
+ reader: None,
+ })));
+
+ let mut inner = socket.0.lock().unwrap();
+ if inner.state != SocketState::Unprepared {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Socket already prepared");
+ return Err(());
+ }
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Preparing socket");
+
+ inner.buffer_pool.set_active(true).map_err(|err| {
+ gst_error!(SOCKET_CAT, obj: &inner.element, "Failed to prepare socket: {}", err);
+ })?;
+ inner.state = SocketState::Prepared;
+ drop(inner);
+
+ Ok(socket)
+ }
+
+ pub fn state(&self) -> SocketState {
+ self.0.lock().unwrap().state
+ }
+
+ pub fn start(
+ &self,
+ clock: Option<gst::Clock>,
+ base_time: Option<gst::ClockTime>,
+ ) -> Result<SocketStream<T>, ()> {
+ // Paused->Playing
+ let mut inner = self.0.lock().unwrap();
+ assert_ne!(SocketState::Unprepared, inner.state);
+ if inner.state == SocketState::Started {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Socket already started");
+ return Err(());
+ }
+
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Starting socket");
+ inner.clock = clock;
+ inner.base_time = base_time;
+ inner.state = SocketState::Started;
+
+ Ok(SocketStream::<T>::new(self))
+ }
+
+ pub fn pause(&self) {
+ // Playing->Paused
+ let mut inner = self.0.lock().unwrap();
+ assert_ne!(SocketState::Unprepared, inner.state);
+ if inner.state != SocketState::Started {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Socket not started");
+ return;
+ }
+
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Pausing socket");
+ inner.clock = None;
+ inner.base_time = None;
+ inner.state = SocketState::Paused;
+ if let Some(read_handle) = inner.read_handle.take() {
+ read_handle.abort();
+ }
+ }
+}
+
+impl<T: SocketRead> Drop for Socket<T> {
+ fn drop(&mut self) {
+ // Ready->Null
+ let mut inner = self.0.lock().unwrap();
+ assert_ne!(SocketState::Started, inner.state);
+ if inner.state == SocketState::Unprepared {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Socket already unprepared");
+ return;
+ }
+
+ if let Some(create_read_handle_handle) = inner.create_read_handle.take() {
+ create_read_handle_handle.abort();
+ }
+
+ if let Err(err) = inner.buffer_pool.set_active(false) {
+ gst_error!(SOCKET_CAT, obj: &inner.element, "Failed to unprepare socket: {}", err);
+ }
+ inner.state = SocketState::Unprepared;
+ }
+}
+
+impl<T: SocketRead + Unpin + 'static> Clone for Socket<T> {
+ fn clone(&self) -> Self {
+ Socket::<T>(self.0.clone())
+ }
+}
+
+pub type SocketStreamItem = Result<(gst::Buffer, Option<std::net::SocketAddr>), SocketError>;
+
+#[derive(Debug)]
+pub enum SocketError {
+ Gst(gst::FlowError),
+ Io(io::Error),
+}
+
+pub struct SocketStream<T: SocketRead + 'static> {
+ socket: Socket<T>,
+ mapped_buffer: Option<gst::MappedBuffer<gst::buffer::Writable>>,
+}
+
+impl<T: SocketRead + 'static> SocketStream<T> {
+ fn new(socket: &Socket<T>) -> Self {
+ SocketStream {
+ socket: socket.clone(),
+ mapped_buffer: None,
+ }
+ }
+
+ #[allow(clippy::should_implement_trait)]
+ pub async fn next(&mut self) -> Option<SocketStreamItem> {
+ // First create if needed
+ let (create_reader_fut, element) = {
+ let mut inner = self.socket.0.lock().unwrap();
+
+ if let Some(create_reader_fut) = inner.create_reader_fut.take() {
+ let (create_reader_fut, abort_handle) = abortable(create_reader_fut);
+ inner.create_read_handle = Some(abort_handle);
+ (Some(create_reader_fut), inner.element.clone())
+ } else {
+ (None, inner.element.clone())
+ }
+ };
+
+ if let Some(create_reader_fut) = create_reader_fut {
+ match create_reader_fut.await {
+ Ok(Ok(read)) => {
+ let mut inner = self.socket.0.lock().unwrap();
+ inner.create_read_handle = None;
+ inner.reader = Some(read);
+ }
+ Ok(Err(err)) => {
+ gst_debug!(SOCKET_CAT, obj: &element, "Create reader error {:?}", err);
+
+ return Some(Err(err));
+ }
+ Err(Aborted) => {
+ gst_debug!(SOCKET_CAT, obj: &element, "Create reader Aborted");
+
+ return None;
+ }
+ }
+ }
+
+ // take the mapped_buffer before locking the socket so as to please the mighty borrow checker
+ let (read_fut, clock, base_time) = {
+ let mut inner = self.socket.0.lock().unwrap();
+ if inner.state != SocketState::Started {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Socket is not Started");
+ return None;
+ }
+
+ let reader = match inner.reader {
+ None => {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Have no reader");
+ return None;
+ }
+ Some(ref reader) => reader,
+ };
+
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Trying to read data");
+ if self.mapped_buffer.is_none() {
+ match inner.buffer_pool.acquire_buffer(None) {
+ Ok(buffer) => {
+ self.mapped_buffer = Some(buffer.into_mapped_buffer_writable().unwrap());
+ }
+ Err(err) => {
+ gst_debug!(SOCKET_CAT, obj: &inner.element, "Failed to acquire buffer {:?}", err);
+ return Some(Err(SocketError::Gst(err)));
+ }
+ }
+ }
+
+ let (read_fut, abort_handle) =
+ abortable(reader.read(self.mapped_buffer.as_mut().unwrap().as_mut_slice()));
+ inner.read_handle = Some(abort_handle);
+
+ (read_fut, inner.clock.clone(), inner.base_time)
+ };
+
+ match read_fut.await {
+ Ok(Ok((len, saddr))) => {
+ let dts = if T::DO_TIMESTAMP {
+ let time = clock.as_ref().unwrap().get_time();
+ let running_time = time - base_time.unwrap();
+ gst_debug!(
+ SOCKET_CAT,
+ obj: &element,
+ "Read {} bytes at {} (clock {})",
+ len,
+ running_time,
+ time
+ );
+ running_time
+ } else {
+ gst_debug!(SOCKET_CAT, obj: &element, "Read {} bytes", len);
+ gst::CLOCK_TIME_NONE
+ };
+
+ let mut buffer = self.mapped_buffer.take().unwrap().into_buffer();
+ {
+ let buffer = buffer.get_mut().unwrap();
+ if len < buffer.get_size() {
+ buffer.set_size(len);
+ }
+ buffer.set_dts(dts);
+ }
+
+ Some(Ok((buffer, saddr)))
+ }
+ Ok(Err(err)) => {
+ gst_debug!(SOCKET_CAT, obj: &element, "Read error {:?}", err);
+
+ Some(Err(SocketError::Io(err)))
+ }
+ Err(Aborted) => {
+ gst_debug!(SOCKET_CAT, obj: &element, "Read Aborted");
+
+ None
+ }
+ }
+ }
+}
+
+// Send/Sync struct for passing around a gio::Socket
+// and getting the raw fd from it
+//
+// gio::Socket is not Send/Sync as it's generally unsafe
+// to access it from multiple threads. Getting the underlying raw
+// fd is safe though, as is receiving/sending from two different threads
+#[derive(Debug)]
+pub struct GioSocketWrapper {
+ socket: *mut gio_ffi::GSocket,
+}
+
+unsafe impl Send for GioSocketWrapper {}
+unsafe impl Sync for GioSocketWrapper {}
+
+impl GioSocketWrapper {
+ pub fn new(socket: &gio::Socket) -> Self {
+ use glib::translate::*;
+
+ Self {
+ socket: socket.to_glib_full(),
+ }
+ }
+
+ pub fn as_socket(&self) -> gio::Socket {
+ unsafe {
+ use glib::translate::*;
+
+ from_glib_none(self.socket)
+ }
+ }
+
+ #[cfg(unix)]
+ pub fn set_tos(&self, qos_dscp: i32) -> Result<(), glib::error::Error> {
+ use libc::{IPPROTO_IP, IPPROTO_IPV6, IPV6_TCLASS, IP_TOS};
+
+ let tos = (qos_dscp & 0x3f) << 2;
+
+ let socket = self.as_socket();
+
+ socket.set_option(IPPROTO_IP, IP_TOS, tos)?;
+
+ if socket.get_family() == gio::SocketFamily::Ipv6 {
+ socket.set_option(IPPROTO_IPV6, IPV6_TCLASS, tos)?;
+ }
+
+ Ok(())
+ }
+
+ #[cfg(not(unix))]
+ pub fn set_tos(&self, qos_dscp: i32) -> Result<(), Error> {
+ Ok(())
+ }
+
+ #[cfg(unix)]
+ pub fn get<T: FromRawFd>(&self) -> T {
+ unsafe { FromRawFd::from_raw_fd(libc::dup(gio_ffi::g_socket_get_fd(self.socket))) }
+ }
+
+ #[cfg(windows)]
+ pub fn get<T: FromRawSocket>(&self) -> T {
+ unsafe {
+ FromRawSocket::from_raw_socket(
+ dup_socket(gio_ffi::g_socket_get_fd(self.socket) as _) as _
+ )
+ }
+ }
+}
+
+impl Clone for GioSocketWrapper {
+ fn clone(&self) -> Self {
+ Self {
+ socket: unsafe { gobject_ffi::g_object_ref(self.socket as *mut _) as *mut _ },
+ }
+ }
+}
+
+impl Drop for GioSocketWrapper {
+ fn drop(&mut self) {
+ unsafe {
+ gobject_ffi::g_object_unref(self.socket as *mut _);
+ }
+ }
+}
+
+#[cfg(windows)]
+unsafe fn dup_socket(socket: usize) -> usize {
+ use std::mem;
+ use winapi::shared::ws2def;
+ use winapi::um::processthreadsapi;
+ use winapi::um::winsock2;
+
+ let mut proto_info = mem::MaybeUninit::uninit();
+ let ret = winsock2::WSADuplicateSocketA(
+ socket,
+ processthreadsapi::GetCurrentProcessId(),
+ proto_info.as_mut_ptr(),
+ );
+ assert_eq!(ret, 0);
+ let mut proto_info = proto_info.assume_init();
+
+ let socket = winsock2::WSASocketA(
+ ws2def::AF_INET,
+ ws2def::SOCK_DGRAM,
+ ws2def::IPPROTO_UDP as i32,
+ &mut proto_info,
+ 0,
+ 0,
+ );
+
+ assert_ne!(socket, winsock2::INVALID_SOCKET);
+
+ socket
+}
+
+pub fn wrap_socket(socket: &tokio::net::UdpSocket) -> Result<GioSocketWrapper, gst::ErrorMessage> {
+ #[cfg(unix)]
+ unsafe {
+ let fd = libc::dup(socket.as_raw_fd());
+
+ // This is unsafe because it allows us to share the fd between the socket and the
+ // GIO socket below, but safety of this is the job of the application
+ struct FdConverter(RawFd);
+ impl IntoRawFd for FdConverter {
+ fn into_raw_fd(self) -> RawFd {
+ self.0
+ }
+ }
+
+ let fd = FdConverter(fd);
+
+ let gio_socket = gio::Socket::new_from_fd(fd).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to create wrapped GIO socket: {}", err]
+ )
+ })?;
+ Ok(GioSocketWrapper::new(&gio_socket))
+ }
+ #[cfg(windows)]
+ unsafe {
+ // FIXME: Needs https://github.com/tokio-rs/tokio/pull/806
+ // and https://github.com/carllerche/mio/pull/859
+ let fd = unreachable!(); //dup_socket(socket.as_raw_socket() as _) as _;
+
+ // This is unsafe because it allows us to share the fd between the socket and the
+ // GIO socket below, but safety of this is the job of the application
+ struct SocketConverter(RawSocket);
+ impl IntoRawSocket for SocketConverter {
+ fn into_raw_socket(self) -> RawSocket {
+ self.0
+ }
+ }
+
+ let fd = SocketConverter(fd);
+
+ let gio_socket = gio::Socket::new_from_socket(fd).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to create wrapped GIO socket: {}", err]
+ )
+ })?;
+
+ Ok(GioSocketWrapper::new(&gio_socket))
+ }
+}
diff --git a/generic/gst-plugin-threadshare/src/tcpclientsrc.rs b/generic/gst-plugin-threadshare/src/tcpclientsrc.rs
new file mode 100644
index 000000000..1a35a668a
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/tcpclientsrc.rs
@@ -0,0 +1,768 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+// Copyright (C) 2018 LEE Dongjun <redongjun@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::BoxFuture;
+use futures::lock::Mutex as FutMutex;
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_log, gst_trace};
+
+use lazy_static::lazy_static;
+
+use rand;
+
+use std::io;
+use std::net::{IpAddr, SocketAddr};
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+use std::u16;
+
+use tokio::io::AsyncReadExt;
+
+use crate::runtime::prelude::*;
+use crate::runtime::{Context, PadSrc, PadSrcRef, Task};
+
+use super::socket::{Socket, SocketError, SocketRead, SocketState};
+
+const DEFAULT_ADDRESS: Option<&str> = Some("127.0.0.1");
+const DEFAULT_PORT: u32 = 5000;
+const DEFAULT_CAPS: Option<gst::Caps> = None;
+const DEFAULT_CHUNK_SIZE: u32 = 4096;
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ address: Option<String>,
+ port: u32,
+ caps: Option<gst::Caps>,
+ chunk_size: u32,
+ context: String,
+ context_wait: u32,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ address: DEFAULT_ADDRESS.map(Into::into),
+ port: DEFAULT_PORT,
+ caps: DEFAULT_CAPS,
+ chunk_size: DEFAULT_CHUNK_SIZE,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 6] = [
+ subclass::Property("address", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Address",
+ "Address to receive packets from",
+ DEFAULT_ADDRESS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("port", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Port",
+ "Port to receive packets from",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_PORT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("caps", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Caps",
+ "Caps to use",
+ gst::Caps::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("chunk-size", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Chunk Size",
+ "Chunk Size",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_CHUNK_SIZE,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+struct TcpClientReaderInner {
+ socket: tokio::net::TcpStream,
+}
+
+pub struct TcpClientReader(Arc<FutMutex<TcpClientReaderInner>>);
+
+impl TcpClientReader {
+ pub fn new(socket: tokio::net::TcpStream) -> Self {
+ TcpClientReader(Arc::new(FutMutex::new(TcpClientReaderInner { socket })))
+ }
+}
+
+impl SocketRead for TcpClientReader {
+ const DO_TIMESTAMP: bool = false;
+
+ fn read<'buf>(
+ &self,
+ buffer: &'buf mut [u8],
+ ) -> BoxFuture<'buf, io::Result<(usize, Option<std::net::SocketAddr>)>> {
+ let this = Arc::clone(&self.0);
+
+ async move {
+ this.lock()
+ .await
+ .socket
+ .read(buffer)
+ .await
+ .map(|read_size| (read_size, None))
+ }
+ .boxed()
+ }
+}
+
+#[derive(Debug)]
+struct TcpClientSrcPadHandlerState {
+ need_initial_events: bool,
+ need_segment: bool,
+ caps: Option<gst::Caps>,
+}
+
+impl Default for TcpClientSrcPadHandlerState {
+ fn default() -> Self {
+ TcpClientSrcPadHandlerState {
+ need_initial_events: true,
+ need_segment: true,
+ caps: None,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct TcpClientSrcPadHandlerInner {
+ state: FutMutex<TcpClientSrcPadHandlerState>,
+ configured_caps: StdMutex<Option<gst::Caps>>,
+}
+
+#[derive(Clone, Debug, Default)]
+struct TcpClientSrcPadHandler(Arc<TcpClientSrcPadHandlerInner>);
+
+impl TcpClientSrcPadHandler {
+ fn prepare(&self, caps: Option<gst::Caps>) {
+ self.0
+ .state
+ .try_lock()
+ .expect("State locked elsewhere")
+ .caps = caps;
+ }
+
+ fn reset(&self) {
+ *self.0.state.try_lock().expect("State locked elsewhere") = Default::default();
+ *self.0.configured_caps.lock().unwrap() = None;
+ }
+
+ fn set_need_segment(&self) {
+ self.0
+ .state
+ .try_lock()
+ .expect("State locked elsewhere")
+ .need_segment = true;
+ }
+
+ async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &gst::Element) {
+ let mut state = self.0.state.lock().await;
+ if state.need_initial_events {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Pushing initial events");
+
+ let stream_id = format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
+ let stream_start_evt = gst::Event::new_stream_start(&stream_id)
+ .group_id(gst::GroupId::next())
+ .build();
+ pad.push_event(stream_start_evt).await;
+
+ if let Some(ref caps) = state.caps {
+ let caps_evt = gst::Event::new_caps(&caps).build();
+ pad.push_event(caps_evt).await;
+ *self.0.configured_caps.lock().unwrap() = Some(caps.clone());
+ }
+
+ state.need_initial_events = false;
+ }
+
+ if state.need_segment {
+ let segment_evt =
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build();
+ pad.push_event(segment_evt).await;
+
+ state.need_segment = false;
+ }
+ }
+
+ async fn push_buffer(
+ &self,
+ pad: &PadSrcRef<'_>,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer);
+
+ self.push_prelude(pad, element).await;
+
+ if buffer.get_size() == 0 {
+ let event = gst::Event::new_eos().build();
+ pad.push_event(event).await;
+ return Ok(gst::FlowSuccess::Ok);
+ }
+
+ pad.push(buffer).await
+ }
+}
+
+impl PadSrcHandler for TcpClientSrcPadHandler {
+ type ElementImpl = TcpClientSrc;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ tcpclientsrc: &TcpClientSrc,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let ret = match event.view() {
+ EventView::FlushStart(..) => {
+ tcpclientsrc.flush_start(element);
+
+ true
+ }
+ EventView::FlushStop(..) => {
+ tcpclientsrc.flush_stop(element);
+
+ true
+ }
+ EventView::Reconfigure(..) => true,
+ EventView::Latency(..) => true,
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", event);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", event);
+ }
+
+ ret
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ _tcpclientsrc: &TcpClientSrc,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+ let ret = match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ q.set(false, 0.into(), gst::CLOCK_TIME_NONE);
+ true
+ }
+ QueryView::Scheduling(ref mut q) => {
+ q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0);
+ q.add_scheduling_modes(&[gst::PadMode::Push]);
+ true
+ }
+ QueryView::Caps(ref mut q) => {
+ let caps = if let Some(caps) = self.0.configured_caps.lock().unwrap().as_ref() {
+ q.get_filter()
+ .map(|f| f.intersect_with_mode(caps, gst::CapsIntersectMode::First))
+ .unwrap_or_else(|| caps.clone())
+ } else {
+ q.get_filter()
+ .map(|f| f.to_owned())
+ .unwrap_or_else(gst::Caps::new_any)
+ };
+
+ q.set_result(&caps);
+
+ true
+ }
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", query);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", query);
+ }
+
+ ret
+ }
+}
+
+struct TcpClientSrc {
+ src_pad: PadSrc,
+ src_pad_handler: TcpClientSrcPadHandler,
+ task: Task,
+ socket: StdMutex<Option<Socket<TcpClientReader>>>,
+ settings: StdMutex<Settings>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-tcpclientsrc",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing TCP Client source"),
+ );
+}
+
+impl TcpClientSrc {
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ let settings = self.settings.lock().unwrap().clone();
+
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ let context =
+ Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ let addr: IpAddr = match settings.address {
+ None => {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["No address set"]
+ ));
+ }
+ Some(ref addr) => match addr.parse() {
+ Err(err) => {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Invalid address '{}' set: {}", addr, err]
+ ));
+ }
+ Ok(addr) => addr,
+ },
+ };
+ let port = settings.port;
+
+ let buffer_pool = gst::BufferPool::new();
+ let mut config = buffer_pool.get_config();
+ config.set_params(None, settings.chunk_size, 0, 0);
+ buffer_pool.set_config(config).map_err(|_| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Failed to configure buffer pool"]
+ )
+ })?;
+
+ let saddr = SocketAddr::new(addr, port as u16);
+ let element_clone = element.clone();
+ let socket = Socket::new(element.upcast_ref(), buffer_pool, async move {
+ gst_debug!(CAT, obj: &element_clone, "Connecting to {:?}", saddr);
+ let socket = tokio::net::TcpStream::connect(saddr)
+ .await
+ .map_err(SocketError::Io)?;
+ Ok(TcpClientReader::new(socket))
+ })
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to prepare socket {:?}", err]
+ )
+ })?;
+
+ *self.socket.lock().unwrap() = Some(socket);
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+ self.src_pad_handler.prepare(settings.caps);
+ self.src_pad.prepare(&self.src_pad_handler);
+
+ gst_debug!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ *self.socket.lock().unwrap() = None;
+
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Stopping");
+
+ self.task.stop();
+ self.src_pad_handler.reset();
+
+ gst_debug!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let socket = self.socket.lock().unwrap();
+ let socket = socket.as_ref().unwrap();
+ if socket.state() == SocketState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return Ok(());
+ }
+
+ gst_debug!(CAT, obj: element, "Starting");
+ self.start_task(element, socket);
+ gst_debug!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_task(&self, element: &gst::Element, socket: &Socket<TcpClientReader>) {
+ let socket_stream = socket
+ .start(element.get_clock(), Some(element.get_base_time()))
+ .unwrap();
+ let socket_stream = Arc::new(FutMutex::new(socket_stream));
+
+ let src_pad_handler = self.src_pad_handler.clone();
+ let pad_weak = self.src_pad.downgrade();
+ let element = element.clone();
+
+ self.task.start(move || {
+ let src_pad_handler = src_pad_handler.clone();
+ let pad_weak = pad_weak.clone();
+ let element = element.clone();
+ let socket_stream = socket_stream.clone();
+
+ async move {
+ let item = socket_stream.lock().await.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+ let buffer = match item {
+ Some(Ok((buffer, _))) => buffer,
+ Some(Err(err)) => {
+ gst_error!(CAT, obj: &element, "Got error {:?}", err);
+ match err {
+ SocketError::Gst(err) => {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ }
+ SocketError::Io(err) => {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("I/O error"),
+ ["streaming stopped, I/O error {}", err]
+ );
+ }
+ }
+ return glib::Continue(false);
+ }
+ None => {
+ gst_log!(CAT, obj: pad.gst_pad(), "SocketStream Stopped");
+ return glib::Continue(false);
+ }
+ };
+
+ match src_pad_handler.push_buffer(&pad, &element, buffer).await {
+ Ok(_) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Successfully pushed buffer");
+ glib::Continue(true)
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Flushing");
+ glib::Continue(false)
+ }
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "EOS");
+ let eos = gst::Event::new_eos().build();
+ pad.push_event(eos).await;
+ glib::Continue(false)
+ }
+ Err(err) => {
+ gst_error!(CAT, obj: pad.gst_pad(), "Got error {}", err);
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ let socket = self.socket.lock().unwrap();
+ if let Some(socket) = socket.as_ref() {
+ if socket.state() == SocketState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(CAT, obj: element, "Stopping Flush");
+
+ self.src_pad_handler.set_need_segment();
+ self.start_task(element, socket);
+
+ gst_debug!(CAT, obj: element, "Stopped Flush");
+ } else {
+ gst_debug!(CAT, obj: element, "Socket not available");
+ }
+ }
+
+ fn flush_start(&self, element: &gst::Element) {
+ let socket = self.socket.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Starting Flush");
+
+ if let Some(socket) = socket.as_ref() {
+ socket.pause();
+ }
+
+ self.task.cancel();
+
+ gst_debug!(CAT, obj: element, "Flush Started");
+ }
+
+ fn pause(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Pausing");
+
+ self.socket.lock().unwrap().as_ref().unwrap().pause();
+ self.task.pause();
+
+ gst_debug!(CAT, obj: element, "Paused");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for TcpClientSrc {
+ const NAME: &'static str = "RsTsTcpClientSrc";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing TCP client source",
+ "Source/Network",
+ "Receives data over the network via TCP",
+ "Sebastian Dröge <sebastian@centricular.com>, LEE Dongjun <redongjun@gmail.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&PROPERTIES);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ let templ = klass.get_pad_template("src").unwrap();
+
+ Self {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(&templ, Some("src"))),
+ src_pad_handler: TcpClientSrcPadHandler::default(),
+ task: Task::default(),
+ socket: StdMutex::new(None),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for TcpClientSrc {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("address", ..) => {
+ settings.address = value.get().expect("type checked upstream");
+ }
+ subclass::Property("port", ..) => {
+ settings.port = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("caps", ..) => {
+ settings.caps = value.get().expect("type checked upstream");
+ }
+ subclass::Property("chunk-size", ..) => {
+ settings.chunk_size = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("address", ..) => Ok(settings.address.to_value()),
+ subclass::Property("port", ..) => Ok(settings.port.to_value()),
+ subclass::Property("caps", ..) => Ok(settings.caps.to_value()),
+ subclass::Property("chunk-size", ..) => Ok(settings.chunk_size.to_value()),
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SOURCE);
+ }
+}
+
+impl ElementImpl for TcpClientSrc {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ self.pause(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToPlaying => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-tcpclientsrc",
+ gst::Rank::None,
+ TcpClientSrc::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/udpsink.rs b/generic/gst-plugin-threadshare/src/udpsink.rs
new file mode 100644
index 000000000..3c95f25c7
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/udpsink.rs
@@ -0,0 +1,1521 @@
+// Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::channel::mpsc;
+use futures::future::BoxFuture;
+use futures::lock::Mutex;
+use futures::prelude::*;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::EventView;
+use gst::{
+ gst_debug, gst_element_error, gst_error, gst_error_msg, gst_info, gst_log, gst_trace,
+ gst_warning,
+};
+
+use lazy_static::lazy_static;
+
+use crate::runtime::prelude::*;
+use crate::runtime::{self, Context, PadSink, PadSinkRef, Task};
+use crate::socket::{wrap_socket, GioSocketWrapper};
+
+use std::convert::TryInto;
+use std::mem;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::string::ToString;
+use std::sync::Mutex as StdMutex;
+use std::sync::{Arc, RwLock};
+use std::time::Duration;
+use std::u16;
+use std::u8;
+
+const DEFAULT_HOST: Option<&str> = Some("127.0.0.1");
+const DEFAULT_PORT: u32 = 5000;
+const DEFAULT_SYNC: bool = true;
+const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0";
+const DEFAULT_BIND_PORT: u32 = 0;
+const DEFAULT_BIND_ADDRESS_V6: &str = "::";
+const DEFAULT_BIND_PORT_V6: u32 = 0;
+const DEFAULT_SOCKET: Option<GioSocketWrapper> = None;
+const DEFAULT_USED_SOCKET: Option<GioSocketWrapper> = None;
+const DEFAULT_SOCKET_V6: Option<GioSocketWrapper> = None;
+const DEFAULT_USED_SOCKET_V6: Option<GioSocketWrapper> = None;
+const DEFAULT_AUTO_MULTICAST: bool = true;
+const DEFAULT_LOOP: bool = true;
+const DEFAULT_TTL: u32 = 64;
+const DEFAULT_TTL_MC: u32 = 1;
+const DEFAULT_QOS_DSCP: i32 = -1;
+const DEFAULT_CLIENTS: &str = "";
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ host: Option<String>,
+ port: u32,
+ sync: bool,
+ bind_address: String,
+ bind_port: u32,
+ bind_address_v6: String,
+ bind_port_v6: u32,
+ socket: Option<GioSocketWrapper>,
+ used_socket: Option<GioSocketWrapper>,
+ socket_v6: Option<GioSocketWrapper>,
+ used_socket_v6: Option<GioSocketWrapper>,
+ auto_multicast: bool,
+ multicast_loop: bool,
+ ttl: u32,
+ ttl_mc: u32,
+ qos_dscp: i32,
+ context: String,
+ context_wait: u32,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ host: DEFAULT_HOST.map(Into::into),
+ port: DEFAULT_PORT,
+ sync: DEFAULT_SYNC,
+ bind_address: DEFAULT_BIND_ADDRESS.into(),
+ bind_port: DEFAULT_BIND_PORT,
+ bind_address_v6: DEFAULT_BIND_ADDRESS_V6.into(),
+ bind_port_v6: DEFAULT_BIND_PORT_V6,
+ socket: DEFAULT_SOCKET,
+ used_socket: DEFAULT_USED_SOCKET,
+ socket_v6: DEFAULT_SOCKET_V6,
+ used_socket_v6: DEFAULT_USED_SOCKET_V6,
+ auto_multicast: DEFAULT_AUTO_MULTICAST,
+ multicast_loop: DEFAULT_LOOP,
+ ttl: DEFAULT_TTL,
+ ttl_mc: DEFAULT_TTL_MC,
+ qos_dscp: DEFAULT_QOS_DSCP,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ }
+ }
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-udpsink",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing UDP sink"),
+ );
+}
+
+static PROPERTIES: [subclass::Property; 19] = [
+ subclass::Property("host", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Host",
+ "The host/IP/Multicast group to send the packets to",
+ DEFAULT_HOST,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("port", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Port",
+ "The port to send the packets to",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_PORT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("sync", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Sync",
+ "Sync on the clock",
+ DEFAULT_SYNC,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("bind-address", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Bind Address",
+ "Address to bind the socket to",
+ Some(DEFAULT_BIND_ADDRESS),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("bind-port", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Bind Port",
+ "Port to bind the socket to",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_BIND_PORT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("bind-address-v6", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Bind Address V6",
+ "Address to bind the V6 socket to",
+ Some(DEFAULT_BIND_ADDRESS_V6),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("bind-port-v6", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Bind Port",
+ "Port to bind the V6 socket to",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_BIND_PORT_V6,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("socket", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Socket",
+ "Socket to use for UDP transmission. (None == allocate)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("used-socket", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Used Socket",
+ "Socket currently in use for UDP transmission. (None = no socket)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READABLE,
+ )
+ }),
+ subclass::Property("socket-v6", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Socket V6",
+ "IPV6 Socket to use for UDP transmission. (None == allocate)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("used-socket-v6", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Used Socket V6",
+ "V6 Socket currently in use for UDP transmission. (None = no socket)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READABLE,
+ )
+ }),
+ subclass::Property("auto-multicast", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Auto multicast",
+ "Automatically join/leave the multicast groups, FALSE means user has to do it himself",
+ DEFAULT_AUTO_MULTICAST,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("loop", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Loop",
+ "Set the multicast loop parameter.",
+ DEFAULT_LOOP,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("ttl", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Time To Live",
+ "Used for setting the unicast TTL parameter",
+ 0,
+ u8::MAX as u32,
+ DEFAULT_TTL,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("ttl-mc", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Time To Live Multicast",
+ "Used for setting the multicast TTL parameter",
+ 0,
+ u8::MAX as u32,
+ DEFAULT_TTL_MC,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("qos-dscp", |name| {
+ glib::ParamSpec::int(
+ name,
+ "QoS DSCP",
+ "Quality of Service, differentiated services code point (-1 default)",
+ -1,
+ 63,
+ DEFAULT_QOS_DSCP,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("clients", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Clients",
+ "A comma separated list of host:port pairs with destinations",
+ Some(DEFAULT_CLIENTS),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Debug)]
+enum TaskItem {
+ Buffer(gst::Buffer),
+ Event(gst::Event),
+}
+
+#[derive(Debug)]
+struct UdpSinkPadHandlerInner {
+ sync: bool,
+ segment: Option<gst::Segment>,
+ latency: gst::ClockTime,
+ socket: Arc<Mutex<Option<tokio::net::UdpSocket>>>,
+ socket_v6: Arc<Mutex<Option<tokio::net::UdpSocket>>>,
+ clients: Arc<Vec<SocketAddr>>,
+ clients_to_configure: Vec<SocketAddr>,
+ clients_to_unconfigure: Vec<SocketAddr>,
+ sender: Arc<Mutex<Option<mpsc::Sender<TaskItem>>>>,
+ settings: Arc<StdMutex<Settings>>,
+}
+
+impl UdpSinkPadHandlerInner {
+ fn new(settings: Arc<StdMutex<Settings>>) -> Self {
+ UdpSinkPadHandlerInner {
+ sync: DEFAULT_SYNC,
+ segment: None,
+ latency: gst::CLOCK_TIME_NONE,
+ socket: Arc::new(Mutex::new(None)),
+ socket_v6: Arc::new(Mutex::new(None)),
+ clients: Arc::new(vec![SocketAddr::new(
+ DEFAULT_HOST.unwrap().parse().unwrap(),
+ DEFAULT_PORT as u16,
+ )]),
+ clients_to_configure: vec![],
+ clients_to_unconfigure: vec![],
+ sender: Arc::new(Mutex::new(None)),
+ settings,
+ }
+ }
+
+ fn clear_clients(
+ &mut self,
+ gst_pad: &gst::Pad,
+ clients_to_add: impl Iterator<Item = SocketAddr>,
+ ) {
+ let old_clients = mem::replace(&mut *Arc::make_mut(&mut self.clients), vec![]);
+
+ self.clients_to_configure = vec![];
+ self.clients_to_unconfigure = vec![];
+
+ for addr in clients_to_add {
+ if !old_clients.contains(&addr) {
+ self.clients_to_unconfigure.push(addr);
+ }
+ self.add_client(gst_pad, addr);
+ }
+ }
+
+ fn remove_client(&mut self, gst_pad: &gst::Pad, addr: SocketAddr) {
+ if !self.clients.contains(&addr) {
+ gst_warning!(CAT, obj: gst_pad, "Not removing unknown client {:?}", &addr);
+ return;
+ }
+
+ gst_info!(CAT, obj: gst_pad, "Removing client {:?}", addr);
+
+ Arc::make_mut(&mut self.clients).retain(|addr2| addr != *addr2);
+
+ self.clients_to_unconfigure.push(addr);
+ self.clients_to_configure.retain(|addr2| addr != *addr2);
+ }
+
+ fn replace_client(
+ &mut self,
+ gst_pad: &gst::Pad,
+ addr: Option<SocketAddr>,
+ new_addr: Option<SocketAddr>,
+ ) {
+ if let Some(addr) = addr {
+ self.remove_client(gst_pad, addr);
+ }
+
+ if let Some(new_addr) = new_addr {
+ self.add_client(gst_pad, new_addr);
+ }
+ }
+
+ fn add_client(&mut self, gst_pad: &gst::Pad, addr: SocketAddr) {
+ if self.clients.contains(&addr) {
+ gst_warning!(CAT, obj: gst_pad, "Not adding client {:?} again", &addr);
+ return;
+ }
+
+ gst_info!(CAT, obj: gst_pad, "Adding client {:?}", addr);
+
+ Arc::make_mut(&mut self.clients).push(addr);
+
+ self.clients_to_configure.push(addr);
+ self.clients_to_unconfigure.retain(|addr2| addr != *addr2);
+ }
+}
+
+#[derive(Debug)]
+enum SocketQualified {
+ Ipv4(tokio::net::UdpSocket),
+ Ipv6(tokio::net::UdpSocket),
+}
+
+#[derive(Clone, Debug)]
+struct UdpSinkPadHandler(Arc<RwLock<UdpSinkPadHandlerInner>>);
+
+impl UdpSinkPadHandler {
+ fn new(settings: Arc<StdMutex<Settings>>) -> UdpSinkPadHandler {
+ Self(Arc::new(RwLock::new(UdpSinkPadHandlerInner::new(settings))))
+ }
+
+ fn set_latency(&self, latency: gst::ClockTime) {
+ self.0.write().unwrap().latency = latency;
+ }
+
+ fn prepare(&self) {
+ let mut inner = self.0.write().unwrap();
+ inner.clients_to_configure = inner.clients.to_vec();
+ }
+
+ fn prepare_socket(&self, socket: SocketQualified) {
+ let mut inner = self.0.write().unwrap();
+
+ match socket {
+ SocketQualified::Ipv4(socket) => inner.socket = Arc::new(Mutex::new(Some(socket))),
+ SocketQualified::Ipv6(socket) => inner.socket_v6 = Arc::new(Mutex::new(Some(socket))),
+ }
+ }
+
+ fn unprepare(&self) {
+ let mut inner = self.0.write().unwrap();
+ *inner = UdpSinkPadHandlerInner::new(Arc::clone(&inner.settings))
+ }
+
+ fn clear_clients(&self, gst_pad: &gst::Pad, clients_to_add: impl Iterator<Item = SocketAddr>) {
+ self.0
+ .write()
+ .unwrap()
+ .clear_clients(gst_pad, clients_to_add);
+ }
+
+ fn remove_client(&self, gst_pad: &gst::Pad, addr: SocketAddr) {
+ self.0.write().unwrap().remove_client(gst_pad, addr);
+ }
+
+ fn replace_client(
+ &self,
+ gst_pad: &gst::Pad,
+ addr: Option<SocketAddr>,
+ new_addr: Option<SocketAddr>,
+ ) {
+ self.0
+ .write()
+ .unwrap()
+ .replace_client(gst_pad, addr, new_addr);
+ }
+
+ fn add_client(&self, gst_pad: &gst::Pad, addr: SocketAddr) {
+ self.0.write().unwrap().add_client(gst_pad, addr);
+ }
+
+ fn get_clients(&self) -> Vec<SocketAddr> {
+ (*self.0.read().unwrap().clients).clone()
+ }
+
+ fn configure_client(
+ &self,
+ settings: &Settings,
+ socket: &mut Option<tokio::net::UdpSocket>,
+ socket_v6: &mut Option<tokio::net::UdpSocket>,
+ client: &SocketAddr,
+ ) -> Result<(), gst::ErrorMessage> {
+ if client.ip().is_multicast() {
+ match client.ip() {
+ IpAddr::V4(addr) => {
+ if let Some(socket) = socket.as_mut() {
+ if settings.auto_multicast {
+ socket
+ .join_multicast_v4(addr, Ipv4Addr::new(0, 0, 0, 0))
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ if settings.multicast_loop {
+ socket.set_multicast_loop_v4(true).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set multicast loop: {}", err]
+ )
+ })?;
+ }
+ socket
+ .set_multicast_ttl_v4(settings.ttl_mc)
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set multicast ttl: {}", err]
+ )
+ })?;
+ }
+ }
+ IpAddr::V6(addr) => {
+ if let Some(socket) = socket_v6.as_mut() {
+ if settings.auto_multicast {
+ socket.join_multicast_v6(&addr, 0).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ if settings.multicast_loop {
+ socket.set_multicast_loop_v6(true).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set multicast loop: {}", err]
+ )
+ })?;
+ }
+ /* FIXME no API for set_multicast_ttl_v6 ? */
+ }
+ }
+ }
+ } else {
+ match client.ip() {
+ IpAddr::V4(_) => {
+ if let Some(socket) = socket.as_mut() {
+ socket.set_ttl(settings.ttl).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set unicast ttl: {}", err]
+ )
+ })?;
+ }
+ }
+ IpAddr::V6(_) => {
+ if let Some(socket) = socket_v6.as_mut() {
+ socket.set_ttl(settings.ttl).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set unicast ttl: {}", err]
+ )
+ })?;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn unconfigure_client(
+ &self,
+ settings: &Settings,
+ socket: &mut Option<tokio::net::UdpSocket>,
+ socket_v6: &mut Option<tokio::net::UdpSocket>,
+ client: &SocketAddr,
+ ) -> Result<(), gst::ErrorMessage> {
+ if client.ip().is_multicast() {
+ match client.ip() {
+ IpAddr::V4(addr) => {
+ if let Some(socket) = socket.as_mut() {
+ if settings.auto_multicast {
+ socket
+ .leave_multicast_v4(addr, Ipv4Addr::new(0, 0, 0, 0))
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ }
+ }
+ IpAddr::V6(addr) => {
+ if let Some(socket) = socket_v6.as_mut() {
+ if settings.auto_multicast {
+ socket.leave_multicast_v6(&addr, 0).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ async fn render(
+ &self,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ let (
+ do_sync,
+ rtime,
+ clients,
+ clients_to_configure,
+ clients_to_unconfigure,
+ socket,
+ socket_v6,
+ settings,
+ ) = {
+ let mut inner = self.0.write().unwrap();
+ let do_sync = inner.sync;
+ let mut rtime: gst::ClockTime = 0.into();
+
+ if let Some(segment) = &inner.segment {
+ if let Some(segment) = segment.downcast_ref::<gst::format::Time>() {
+ rtime = segment.to_running_time(buffer.get_pts());
+ if inner.latency.is_some() {
+ rtime += inner.latency;
+ }
+ }
+ }
+
+ let clients_to_configure = mem::replace(&mut inner.clients_to_configure, vec![]);
+ let clients_to_unconfigure = mem::replace(&mut inner.clients_to_unconfigure, vec![]);
+
+ let settings = inner.settings.lock().unwrap().clone();
+
+ (
+ do_sync,
+ rtime,
+ Arc::clone(&inner.clients),
+ clients_to_configure,
+ clients_to_unconfigure,
+ Arc::clone(&inner.socket),
+ Arc::clone(&inner.socket_v6),
+ settings,
+ )
+ };
+
+ let mut socket = socket.lock().await;
+ let mut socket_v6 = socket_v6.lock().await;
+
+ if !clients_to_configure.is_empty() {
+ for client in &clients_to_configure {
+ self.configure_client(&settings, &mut socket, &mut socket_v6, &client)
+ .map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ["Failed to configure client {:?}: {}", client, err]
+ );
+
+ gst::FlowError::Error
+ })?;
+ }
+ }
+
+ if !clients_to_unconfigure.is_empty() {
+ for client in &clients_to_unconfigure {
+ self.unconfigure_client(&settings, &mut socket, &mut socket_v6, &client)
+ .map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ["Failed to unconfigure client {:?}: {}", client, err]
+ );
+
+ gst::FlowError::Error
+ })?;
+ }
+ }
+
+ if do_sync {
+ self.sync(&element, rtime).await;
+ }
+
+ let data = buffer.map_readable().map_err(|_| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Format,
+ ["Failed to map buffer readable"]
+ );
+
+ gst::FlowError::Error
+ })?;
+
+ for client in clients.iter() {
+ let socket = match client.ip() {
+ IpAddr::V4(_) => &mut socket,
+ IpAddr::V6(_) => &mut socket_v6,
+ };
+
+ if let Some(socket) = socket.as_mut() {
+ gst_log!(CAT, obj: element, "Sending to {:?}", &client);
+ socket.send_to(&data, client).await.map_err(|err| {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("I/O error"),
+ ["streaming stopped, I/O error {}", err]
+ );
+ gst::FlowError::Error
+ })?;
+ } else {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("I/O error"),
+ ["No socket available for sending to {}", client]
+ );
+ return Err(gst::FlowError::Error);
+ }
+ }
+
+ gst_log!(
+ CAT,
+ obj: element,
+ "Sent buffer {:?} to all clients",
+ &buffer
+ );
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+
+ /* Wait until specified time */
+ async fn sync(&self, element: &gst::Element, running_time: gst::ClockTime) {
+ let now = super::get_current_running_time(&element);
+
+ if now < running_time {
+ let delay = running_time - now;
+ runtime::time::delay_for(Duration::from_nanos(delay.nseconds().unwrap())).await;
+ }
+ }
+
+ async fn handle_event(&self, element: &gst::Element, event: gst::Event) {
+ match event.view() {
+ EventView::Eos(_) => {
+ let _ = element.post_message(&gst::Message::new_eos().src(Some(element)).build());
+ }
+ EventView::Segment(e) => {
+ self.0.write().unwrap().segment = Some(e.get_segment().clone());
+ }
+ _ => (),
+ }
+ }
+}
+
+impl PadSinkHandler for UdpSinkPadHandler {
+ type ElementImpl = UdpSink;
+
+ fn sink_chain(
+ &self,
+ _pad: &PadSinkRef,
+ _udpsink: &UdpSink,
+ _element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let sender = Arc::clone(&self.0.read().unwrap().sender);
+
+ async move {
+ if let Some(sender) = sender.lock().await.as_mut() {
+ sender.send(TaskItem::Buffer(buffer)).await.unwrap();
+ }
+ Ok(gst::FlowSuccess::Ok)
+ }
+ .boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ _pad: &PadSinkRef,
+ _udpsink: &UdpSink,
+ _element: &gst::Element,
+ list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let sender = Arc::clone(&self.0.read().unwrap().sender);
+
+ async move {
+ if let Some(sender) = sender.lock().await.as_mut() {
+ for buffer in list.iter_owned() {
+ sender.send(TaskItem::Buffer(buffer)).await.unwrap();
+ }
+ }
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+ .boxed()
+ }
+
+ fn sink_event_serialized(
+ &self,
+ _pad: &PadSinkRef,
+ _udpsink: &UdpSink,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ let sender = Arc::clone(&self.0.read().unwrap().sender);
+ let element = element.clone();
+
+ async move {
+ if let EventView::FlushStop(_) = event.view() {
+ let udpsink = UdpSink::from_instance(&element);
+ let _ = udpsink.start(&element);
+ } else if let Some(sender) = sender.lock().await.as_mut() {
+ sender.send(TaskItem::Event(event)).await.unwrap();
+ }
+
+ true
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ _pad: &PadSinkRef,
+ udpsink: &UdpSink,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ if let EventView::FlushStart(..) = event.view() {
+ let _ = udpsink.stop(&element);
+ }
+
+ true
+ }
+}
+
+#[derive(Debug)]
+enum SocketFamily {
+ Ipv4,
+ Ipv6,
+}
+
+#[derive(Debug)]
+struct UdpSink {
+ sink_pad: PadSink,
+ sink_pad_handler: UdpSinkPadHandler,
+ task: Task,
+ settings: Arc<StdMutex<Settings>>,
+}
+
+impl UdpSink {
+ fn prepare_socket(
+ &self,
+ family: SocketFamily,
+ context: &Context,
+ element: &gst::Element,
+ ) -> Result<(), gst::ErrorMessage> {
+ let mut settings = self.settings.lock().unwrap();
+
+ let wrapped_socket = match family {
+ SocketFamily::Ipv4 => &settings.socket,
+ SocketFamily::Ipv6 => &settings.socket_v6,
+ };
+
+ let socket_qualified: SocketQualified;
+
+ if let Some(ref wrapped_socket) = wrapped_socket {
+ use std::net::UdpSocket;
+
+ let socket: UdpSocket;
+
+ #[cfg(unix)]
+ {
+ socket = wrapped_socket.get()
+ }
+ #[cfg(windows)]
+ {
+ socket = wrapped_socket.get()
+ }
+
+ let socket = context.enter(|| {
+ tokio::net::UdpSocket::from_std(socket).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to setup socket for tokio: {}", err]
+ )
+ })
+ })?;
+
+ match family {
+ SocketFamily::Ipv4 => {
+ settings.used_socket = Some(wrapped_socket.clone());
+ socket_qualified = SocketQualified::Ipv4(socket);
+ }
+ SocketFamily::Ipv6 => {
+ settings.used_socket_v6 = Some(wrapped_socket.clone());
+ socket_qualified = SocketQualified::Ipv6(socket);
+ }
+ }
+ } else {
+ let bind_addr = match family {
+ SocketFamily::Ipv4 => &settings.bind_address,
+ SocketFamily::Ipv6 => &settings.bind_address_v6,
+ };
+
+ let bind_addr: IpAddr = bind_addr.parse().map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Invalid address '{}' set: {}", bind_addr, err]
+ )
+ })?;
+
+ let bind_port = match family {
+ SocketFamily::Ipv4 => settings.bind_port,
+ SocketFamily::Ipv6 => settings.bind_port_v6,
+ };
+
+ let saddr = SocketAddr::new(bind_addr, bind_port as u16);
+ gst_debug!(CAT, obj: element, "Binding to {:?}", saddr);
+
+ let builder = match family {
+ SocketFamily::Ipv4 => net2::UdpBuilder::new_v4(),
+ SocketFamily::Ipv6 => net2::UdpBuilder::new_v6(),
+ };
+
+ let builder = match builder {
+ Ok(builder) => builder,
+ Err(err) => {
+ gst_warning!(
+ CAT,
+ obj: element,
+ "Failed to create {} socket builder: {}",
+ match family {
+ SocketFamily::Ipv4 => "IPv4",
+ SocketFamily::Ipv6 => "IPv6",
+ },
+ err
+ );
+ return Ok(());
+ }
+ };
+
+ let socket = builder.bind(&saddr).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to bind socket: {}", err]
+ )
+ })?;
+
+ let socket = context.enter(|| {
+ tokio::net::UdpSocket::from_std(socket).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to setup socket for tokio: {}", err]
+ )
+ })
+ })?;
+
+ let wrapper = wrap_socket(&socket)?;
+
+ if settings.qos_dscp != -1 {
+ wrapper.set_tos(settings.qos_dscp).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to set QoS DSCP: {}", err]
+ )
+ })?;
+ }
+
+ match family {
+ SocketFamily::Ipv4 => {
+ settings.used_socket = Some(wrapper);
+ socket_qualified = SocketQualified::Ipv4(socket)
+ }
+ SocketFamily::Ipv6 => {
+ settings.used_socket_v6 = Some(wrapper);
+ socket_qualified = SocketQualified::Ipv6(socket)
+ }
+ }
+ }
+
+ self.sink_pad_handler.prepare_socket(socket_qualified);
+
+ Ok(())
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ let context = {
+ let settings = self.settings.lock().unwrap();
+
+ Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenWrite,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?
+ };
+
+ self.sink_pad_handler.prepare();
+ self.prepare_socket(SocketFamily::Ipv4, &context, element)?;
+ self.prepare_socket(SocketFamily::Ipv6, &context, element)?;
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+
+ self.sink_pad.prepare(&self.sink_pad_handler);
+
+ gst_debug!(CAT, obj: element, "Started preparing");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ self.task.unprepare().unwrap();
+ self.sink_pad_handler.unprepare();
+ self.sink_pad.unprepare();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Stopping");
+ self.task.stop();
+ gst_debug!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Starting");
+
+ let sink_pad_handler = self.sink_pad_handler.clone();
+ let element_clone = element.clone();
+
+ let (sender, receiver) = mpsc::channel(0);
+ let receiver = Arc::new(Mutex::new(receiver));
+
+ sink_pad_handler.0.write().unwrap().sender = Arc::new(Mutex::new(Some(sender)));
+
+ self.task.start(move || {
+ let receiver = Arc::clone(&receiver);
+ let element = element_clone.clone();
+ let sink_pad_handler = sink_pad_handler.clone();
+
+ async move {
+ match receiver.lock().await.next().await {
+ Some(TaskItem::Buffer(buffer)) => {
+ match sink_pad_handler.render(&element, buffer).await {
+ Err(err) => {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ["Failed to render item, stopping task: {}", err]
+ );
+
+ glib::Continue(false)
+ }
+ _ => glib::Continue(true),
+ }
+ }
+ Some(TaskItem::Event(event)) => {
+ sink_pad_handler.handle_event(&element, event).await;
+ glib::Continue(true)
+ }
+ None => glib::Continue(false),
+ }
+ }
+ });
+
+ Ok(())
+ }
+}
+
+impl UdpSink {
+ fn clear_clients(&self, clients_to_add: impl Iterator<Item = SocketAddr>) {
+ self.sink_pad_handler
+ .clear_clients(&self.sink_pad.gst_pad(), clients_to_add);
+ }
+
+ fn remove_client(&self, addr: SocketAddr) {
+ self.sink_pad_handler
+ .remove_client(&self.sink_pad.gst_pad(), addr);
+ }
+
+ fn replace_client(&self, addr: Option<SocketAddr>, new_addr: Option<SocketAddr>) {
+ self.sink_pad_handler
+ .replace_client(&self.sink_pad.gst_pad(), addr, new_addr);
+ }
+
+ fn add_client(&self, addr: SocketAddr) {
+ self.sink_pad_handler
+ .add_client(&self.sink_pad.gst_pad(), addr);
+ }
+}
+
+fn try_into_socket_addr(element: &gst::Element, host: &str, port: u32) -> Result<SocketAddr, ()> {
+ let addr: IpAddr = match host.parse() {
+ Err(err) => {
+ gst_error!(CAT, obj: element, "Failed to parse host {}: {}", host, err);
+ return Err(());
+ }
+ Ok(addr) => addr,
+ };
+
+ let port: u16 = match port.try_into() {
+ Err(err) => {
+ gst_error!(CAT, obj: element, "Invalid port {}: {}", port, err);
+ return Err(());
+ }
+ Ok(port) => port,
+ };
+
+ Ok(SocketAddr::new(addr, port))
+}
+
+impl ObjectSubclass for UdpSink {
+ const NAME: &'static str = "RsTsUdpSink";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing UDP sink",
+ "Sink/Network",
+ "Thread-sharing UDP sink",
+ "Mathieu <mathieu@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+ klass.add_signal_with_class_handler(
+ "add",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[String::static_type(), i32::static_type()],
+ glib::types::Type::Unit,
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let host = args[1]
+ .get::<String>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let port = args[2]
+ .get::<i32>()
+ .expect("signal arg")
+ .expect("missing signal arg") as u32;
+
+ if let Ok(addr) = try_into_socket_addr(&element, &host, port) {
+ let udpsink = Self::from_instance(&element);
+ udpsink.add_client(addr);
+ }
+
+ None
+ },
+ );
+
+ klass.add_signal_with_class_handler(
+ "remove",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[String::static_type(), i32::static_type()],
+ glib::types::Type::Unit,
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let host = args[1]
+ .get::<String>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+ let port = args[2]
+ .get::<i32>()
+ .expect("signal arg")
+ .expect("missing signal arg") as u32;
+
+ let udpsink = Self::from_instance(&element);
+ let settings = udpsink.settings.lock().unwrap();
+
+ if Some(&host) != settings.host.as_ref() || port != settings.port {
+ if let Ok(addr) = try_into_socket_addr(&element, &host, port) {
+ udpsink.remove_client(addr);
+ }
+ }
+
+ None
+ },
+ );
+
+ klass.add_signal_with_class_handler(
+ "clear",
+ glib::SignalFlags::RUN_LAST | glib::SignalFlags::ACTION,
+ &[],
+ glib::types::Type::Unit,
+ |_, args| {
+ let element = args[0]
+ .get::<gst::Element>()
+ .expect("signal arg")
+ .expect("missing signal arg");
+
+ let udpsink = Self::from_instance(&element);
+ let settings = udpsink.settings.lock().unwrap();
+ let current_client = settings
+ .host
+ .iter()
+ .filter_map(|host| try_into_socket_addr(&element, host, settings.port).ok());
+
+ udpsink.clear_clients(current_client);
+
+ None
+ },
+ );
+
+ klass.install_properties(&PROPERTIES);
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ let settings = Arc::new(StdMutex::new(Settings::default()));
+
+ Self {
+ sink_pad: PadSink::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("sink").unwrap(),
+ Some("sink"),
+ )),
+ sink_pad_handler: UdpSinkPadHandler::new(Arc::clone(&settings)),
+ task: Task::default(),
+ settings,
+ }
+ }
+}
+
+impl ObjectImpl for UdpSink {
+ glib_object_impl!();
+
+ fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("host", ..) => {
+ let current_client = settings
+ .host
+ .as_ref()
+ .and_then(|host| try_into_socket_addr(&element, host, settings.port).ok());
+
+ let new_host = value.get().expect("type checked upstream");
+
+ let new_client = new_host
+ .and_then(|host| try_into_socket_addr(&element, host, settings.port).ok());
+
+ self.replace_client(current_client, new_client);
+
+ settings.host = new_host.map(ToString::to_string);
+ }
+ subclass::Property("port", ..) => {
+ let current_client = settings
+ .host
+ .as_ref()
+ .and_then(|host| try_into_socket_addr(&element, host, settings.port).ok());
+
+ let new_port = value.get_some().expect("type checked upstream");
+
+ let new_client = settings
+ .host
+ .as_ref()
+ .and_then(|host| try_into_socket_addr(&element, host, new_port).ok());
+
+ self.replace_client(current_client, new_client);
+
+ settings.port = new_port;
+ }
+ subclass::Property("sync", ..) => {
+ settings.sync = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("bind-address", ..) => {
+ settings.bind_address = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("bind-port", ..) => {
+ settings.bind_port = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("bind-address-v6", ..) => {
+ settings.bind_address_v6 = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("bind-port-v6", ..) => {
+ settings.bind_port_v6 = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("socket", ..) => {
+ settings.socket = value
+ .get::<gio::Socket>()
+ .expect("type checked upstream")
+ .map(|socket| GioSocketWrapper::new(&socket));
+ }
+ subclass::Property("used-socket", ..) => {
+ unreachable!();
+ }
+ subclass::Property("socket-v6", ..) => {
+ settings.socket_v6 = value
+ .get::<gio::Socket>()
+ .expect("type checked upstream")
+ .map(|socket| GioSocketWrapper::new(&socket));
+ }
+ subclass::Property("used-socket-v6", ..) => {
+ unreachable!();
+ }
+ subclass::Property("auto-multicast", ..) => {
+ settings.auto_multicast = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("loop", ..) => {
+ settings.multicast_loop = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("ttl", ..) => {
+ settings.ttl = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("ttl-mc", ..) => {
+ settings.ttl_mc = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("qos-dscp", ..) => {
+ settings.qos_dscp = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("clients", ..) => {
+ let clients: String = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+
+ let current_client = settings
+ .host
+ .iter()
+ .filter_map(|host| try_into_socket_addr(&element, host, settings.port).ok());
+
+ let clients_iter = current_client.chain(clients.split(',').filter_map(|client| {
+ let rsplit: Vec<&str> = client.rsplitn(2, ':').collect();
+
+ if rsplit.len() == 2 {
+ rsplit[0]
+ .parse::<u32>()
+ .map_err(|err| {
+ gst_error!(
+ CAT,
+ obj: element,
+ "Invalid port {}: {}",
+ rsplit[0],
+ err
+ );
+ })
+ .and_then(|port| try_into_socket_addr(&element, rsplit[1], port))
+ .ok()
+ } else {
+ None
+ }
+ }));
+
+ self.clear_clients(clients_iter);
+ }
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("host", ..) => Ok(settings.host.to_value()),
+ subclass::Property("port", ..) => Ok(settings.port.to_value()),
+ subclass::Property("sync", ..) => Ok(settings.sync.to_value()),
+ subclass::Property("bind-address", ..) => Ok(settings.bind_address.to_value()),
+ subclass::Property("bind-port", ..) => Ok(settings.bind_port.to_value()),
+ subclass::Property("bind-address-v6", ..) => Ok(settings.bind_address_v6.to_value()),
+ subclass::Property("bind-port-v6", ..) => Ok(settings.bind_port_v6.to_value()),
+ subclass::Property("socket", ..) => Ok(settings
+ .socket
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("used-socket", ..) => Ok(settings
+ .used_socket
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("socket-v6", ..) => Ok(settings
+ .socket_v6
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("used-socket-v6", ..) => Ok(settings
+ .used_socket_v6
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("auto-multicast", ..) => Ok(settings.sync.to_value()),
+ subclass::Property("loop", ..) => Ok(settings.multicast_loop.to_value()),
+ subclass::Property("ttl", ..) => Ok(settings.ttl.to_value()),
+ subclass::Property("ttl-mc", ..) => Ok(settings.ttl_mc.to_value()),
+ subclass::Property("qos-dscp", ..) => Ok(settings.qos_dscp.to_value()),
+ subclass::Property("clients", ..) => {
+ let clients: Vec<String> = self
+ .sink_pad_handler
+ .get_clients()
+ .iter()
+ .map(ToString::to_string)
+ .collect();
+
+ Ok(clients.join(",").to_value())
+ }
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.sink_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SINK);
+ }
+}
+
+impl ElementImpl for UdpSink {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::ReadyToPaused => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ self.parent_change_state(element, transition)
+ }
+
+ fn send_event(&self, _element: &gst::Element, event: gst::Event) -> bool {
+ match event.view() {
+ EventView::Latency(ev) => {
+ self.sink_pad_handler.set_latency(ev.get_latency());
+ self.sink_pad.gst_pad().push_event(event)
+ }
+ EventView::Step(..) => false,
+ _ => self.sink_pad.gst_pad().push_event(event),
+ }
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-udpsink",
+ gst::Rank::None,
+ UdpSink::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/src/udpsrc.rs b/generic/gst-plugin-threadshare/src/udpsrc.rs
new file mode 100644
index 000000000..6542b0baa
--- /dev/null
+++ b/generic/gst-plugin-threadshare/src/udpsrc.rs
@@ -0,0 +1,1002 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::future::BoxFuture;
+use futures::lock::Mutex as FutMutex;
+use futures::prelude::*;
+
+use gio;
+
+use glib;
+use glib::prelude::*;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_log, gst_trace};
+use gst_net::*;
+
+use lazy_static::lazy_static;
+
+use rand;
+
+use std::io;
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+use std::u16;
+
+use crate::runtime::prelude::*;
+use crate::runtime::{Context, PadSrc, PadSrcRef, Task};
+
+use super::socket::{wrap_socket, GioSocketWrapper, Socket, SocketError, SocketRead, SocketState};
+
+const DEFAULT_ADDRESS: Option<&str> = Some("127.0.0.1");
+const DEFAULT_PORT: u32 = 5000;
+const DEFAULT_REUSE: bool = true;
+const DEFAULT_CAPS: Option<gst::Caps> = None;
+const DEFAULT_MTU: u32 = 1500;
+const DEFAULT_SOCKET: Option<GioSocketWrapper> = None;
+const DEFAULT_USED_SOCKET: Option<GioSocketWrapper> = None;
+const DEFAULT_CONTEXT: &str = "";
+const DEFAULT_CONTEXT_WAIT: u32 = 0;
+const DEFAULT_RETRIEVE_SENDER_ADDRESS: bool = true;
+
+#[derive(Debug, Clone)]
+struct Settings {
+ address: Option<String>,
+ port: u32,
+ reuse: bool,
+ caps: Option<gst::Caps>,
+ mtu: u32,
+ socket: Option<GioSocketWrapper>,
+ used_socket: Option<GioSocketWrapper>,
+ context: String,
+ context_wait: u32,
+ retrieve_sender_address: bool,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Settings {
+ address: DEFAULT_ADDRESS.map(Into::into),
+ port: DEFAULT_PORT,
+ reuse: DEFAULT_REUSE,
+ caps: DEFAULT_CAPS,
+ mtu: DEFAULT_MTU,
+ socket: DEFAULT_SOCKET,
+ used_socket: DEFAULT_USED_SOCKET,
+ context: DEFAULT_CONTEXT.into(),
+ context_wait: DEFAULT_CONTEXT_WAIT,
+ retrieve_sender_address: DEFAULT_RETRIEVE_SENDER_ADDRESS,
+ }
+ }
+}
+
+static PROPERTIES: [subclass::Property; 10] = [
+ subclass::Property("address", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Address",
+ "Address/multicast group to listen on",
+ DEFAULT_ADDRESS,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("port", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Port",
+ "Port to listen on",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_PORT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("reuse", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Reuse",
+ "Allow reuse of the port",
+ DEFAULT_REUSE,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("caps", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Caps",
+ "Caps to use",
+ gst::Caps::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("mtu", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "MTU",
+ "MTU",
+ 0,
+ u16::MAX as u32,
+ DEFAULT_MTU,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("socket", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Socket",
+ "Socket to use for UDP reception. (None == allocate)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("used-socket", |name| {
+ glib::ParamSpec::object(
+ name,
+ "Used Socket",
+ "Socket currently in use for UDP reception. (None = no socket)",
+ gio::Socket::static_type(),
+ glib::ParamFlags::READABLE,
+ )
+ }),
+ subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("context-wait", |name| {
+ glib::ParamSpec::uint(
+ name,
+ "Context Wait",
+ "Throttle poll loop to run at most once every this many ms",
+ 0,
+ 1000,
+ DEFAULT_CONTEXT_WAIT,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+ subclass::Property("retrieve-sender-address", |name| {
+ glib::ParamSpec::boolean(
+ name,
+ "Retrieve sender address",
+ "Whether to retrieve the sender address and add it to buffers as meta. Disabling this might result in minor performance improvements in certain scenarios",
+ DEFAULT_REUSE,
+ glib::ParamFlags::READWRITE,
+ )
+ }),
+];
+
+#[derive(Debug)]
+struct UdpReaderInner {
+ socket: tokio::net::UdpSocket,
+}
+
+#[derive(Debug)]
+pub struct UdpReader(Arc<FutMutex<UdpReaderInner>>);
+
+impl UdpReader {
+ fn new(socket: tokio::net::UdpSocket) -> Self {
+ UdpReader(Arc::new(FutMutex::new(UdpReaderInner { socket })))
+ }
+}
+
+impl SocketRead for UdpReader {
+ const DO_TIMESTAMP: bool = true;
+
+ fn read<'buf>(
+ &self,
+ buffer: &'buf mut [u8],
+ ) -> BoxFuture<'buf, io::Result<(usize, Option<std::net::SocketAddr>)>> {
+ let this = Arc::clone(&self.0);
+
+ async move {
+ this.lock()
+ .await
+ .socket
+ .recv_from(buffer)
+ .await
+ .map(|(read_size, saddr)| (read_size, Some(saddr)))
+ }
+ .boxed()
+ }
+}
+
+#[derive(Debug)]
+struct UdpSrcPadHandlerState {
+ retrieve_sender_address: bool,
+ need_initial_events: bool,
+ need_segment: bool,
+ caps: Option<gst::Caps>,
+}
+
+impl Default for UdpSrcPadHandlerState {
+ fn default() -> Self {
+ UdpSrcPadHandlerState {
+ retrieve_sender_address: true,
+ need_initial_events: true,
+ need_segment: true,
+ caps: None,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct UdpSrcPadHandlerInner {
+ state: FutMutex<UdpSrcPadHandlerState>,
+ configured_caps: StdMutex<Option<gst::Caps>>,
+}
+
+#[derive(Clone, Debug, Default)]
+struct UdpSrcPadHandler(Arc<UdpSrcPadHandlerInner>);
+
+impl UdpSrcPadHandler {
+ fn prepare(&self, caps: Option<gst::Caps>, retrieve_sender_address: bool) {
+ let mut state = self.0.state.try_lock().expect("State locked elsewhere");
+
+ state.caps = caps;
+ state.retrieve_sender_address = retrieve_sender_address;
+ }
+
+ fn reset(&self) {
+ *self.0.state.try_lock().expect("State locked elsewhere") = Default::default();
+ *self.0.configured_caps.lock().unwrap() = None;
+ }
+
+ fn set_need_segment(&self) {
+ self.0
+ .state
+ .try_lock()
+ .expect("State locked elsewhere")
+ .need_segment = true;
+ }
+
+ async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &gst::Element) {
+ let mut state = self.0.state.lock().await;
+ if state.need_initial_events {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Pushing initial events");
+
+ let stream_id = format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
+ let stream_start_evt = gst::Event::new_stream_start(&stream_id)
+ .group_id(gst::GroupId::next())
+ .build();
+ pad.push_event(stream_start_evt).await;
+
+ if let Some(ref caps) = state.caps {
+ let caps_evt = gst::Event::new_caps(&caps).build();
+ pad.push_event(caps_evt).await;
+ *self.0.configured_caps.lock().unwrap() = Some(caps.clone());
+ }
+
+ state.need_initial_events = false;
+ }
+
+ if state.need_segment {
+ let segment_evt =
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build();
+ pad.push_event(segment_evt).await;
+
+ state.need_segment = false;
+ }
+ }
+
+ async fn push_buffer(
+ &self,
+ pad: &PadSrcRef<'_>,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer);
+
+ self.push_prelude(pad, element).await;
+
+ pad.push(buffer).await
+ }
+}
+
+impl PadSrcHandler for UdpSrcPadHandler {
+ type ElementImpl = UdpSrc;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ udpsrc: &UdpSrc,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ use gst::EventView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let ret = match event.view() {
+ EventView::FlushStart(..) => {
+ udpsrc.flush_start(element);
+
+ true
+ }
+ EventView::FlushStop(..) => {
+ udpsrc.flush_stop(element);
+
+ true
+ }
+ EventView::Reconfigure(..) => true,
+ EventView::Latency(..) => true,
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", event);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", event);
+ }
+
+ ret
+ }
+
+ fn src_query(
+ &self,
+ pad: &PadSrcRef,
+ _udpsrc: &UdpSrc,
+ _element: &gst::Element,
+ query: &mut gst::QueryRef,
+ ) -> bool {
+ use gst::QueryView;
+
+ gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", query);
+
+ let ret = match query.view_mut() {
+ QueryView::Latency(ref mut q) => {
+ q.set(true, 0.into(), gst::CLOCK_TIME_NONE);
+ true
+ }
+ QueryView::Scheduling(ref mut q) => {
+ q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0);
+ q.add_scheduling_modes(&[gst::PadMode::Push]);
+ true
+ }
+ QueryView::Caps(ref mut q) => {
+ let caps = if let Some(caps) = self.0.configured_caps.lock().unwrap().as_ref() {
+ q.get_filter()
+ .map(|f| f.intersect_with_mode(caps, gst::CapsIntersectMode::First))
+ .unwrap_or_else(|| caps.clone())
+ } else {
+ q.get_filter()
+ .map(|f| f.to_owned())
+ .unwrap_or_else(gst::Caps::new_any)
+ };
+
+ q.set_result(&caps);
+
+ true
+ }
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(CAT, obj: pad.gst_pad(), "Handled {:?}", query);
+ } else {
+ gst_log!(CAT, obj: pad.gst_pad(), "Didn't handle {:?}", query);
+ }
+
+ ret
+ }
+}
+
+struct UdpSrc {
+ src_pad: PadSrc,
+ src_pad_handler: UdpSrcPadHandler,
+ task: Task,
+ socket: StdMutex<Option<Socket<UdpReader>>>,
+ settings: StdMutex<Settings>,
+}
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-udpsrc",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing UDP source"),
+ );
+}
+
+impl UdpSrc {
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ let mut settings = self.settings.lock().unwrap().clone();
+
+ gst_debug!(CAT, obj: element, "Preparing");
+
+ let context =
+ Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ let socket = if let Some(ref wrapped_socket) = settings.socket {
+ use std::net::UdpSocket;
+
+ let socket: UdpSocket;
+
+ #[cfg(unix)]
+ {
+ socket = wrapped_socket.get()
+ }
+ #[cfg(windows)]
+ {
+ socket = wrapped_socket.get()
+ }
+
+ let socket = context.enter(|| {
+ tokio::net::UdpSocket::from_std(socket).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to setup socket for tokio: {}", err]
+ )
+ })
+ })?;
+
+ settings.used_socket = Some(wrapped_socket.clone());
+
+ socket
+ } else {
+ let addr: IpAddr = match settings.address {
+ None => {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["No address set"]
+ ));
+ }
+ Some(ref addr) => match addr.parse() {
+ Err(err) => {
+ return Err(gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Invalid address '{}' set: {}", addr, err]
+ ));
+ }
+ Ok(addr) => addr,
+ },
+ };
+ let port = settings.port;
+
+ // TODO: TTL, multicast loopback, etc
+ let saddr = if addr.is_multicast() {
+ let bind_addr = if addr.is_ipv4() {
+ IpAddr::V4(Ipv4Addr::UNSPECIFIED)
+ } else {
+ IpAddr::V6(Ipv6Addr::UNSPECIFIED)
+ };
+
+ let saddr = SocketAddr::new(bind_addr, port as u16);
+ gst_debug!(
+ CAT,
+ obj: element,
+ "Binding to {:?} for multicast group {:?}",
+ saddr,
+ addr
+ );
+
+ saddr
+ } else {
+ let saddr = SocketAddr::new(addr, port as u16);
+ gst_debug!(CAT, obj: element, "Binding to {:?}", saddr);
+
+ saddr
+ };
+
+ let builder = if addr.is_ipv4() {
+ net2::UdpBuilder::new_v4()
+ } else {
+ net2::UdpBuilder::new_v6()
+ }
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to create socket: {}", err]
+ )
+ })?;
+
+ builder.reuse_address(settings.reuse).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to set reuse_address: {}", err]
+ )
+ })?;
+
+ #[cfg(unix)]
+ {
+ use net2::unix::UnixUdpBuilderExt;
+
+ builder.reuse_port(settings.reuse).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to set reuse_port: {}", err]
+ )
+ })?;
+ }
+
+ let socket = builder.bind(&saddr).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to bind socket: {}", err]
+ )
+ })?;
+
+ let socket = context.enter(|| {
+ tokio::net::UdpSocket::from_std(socket).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to setup socket for tokio: {}", err]
+ )
+ })
+ })?;
+
+ if addr.is_multicast() {
+ // TODO: Multicast interface configuration, going to be tricky
+ match addr {
+ IpAddr::V4(addr) => {
+ socket
+ .join_multicast_v4(addr, Ipv4Addr::new(0, 0, 0, 0))
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ IpAddr::V6(addr) => {
+ socket.join_multicast_v6(&addr, 0).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to join multicast group: {}", err]
+ )
+ })?;
+ }
+ }
+ }
+
+ settings.used_socket = Some(wrap_socket(&socket)?);
+
+ socket
+ };
+
+ let buffer_pool = gst::BufferPool::new();
+ let mut config = buffer_pool.get_config();
+ config.set_params(None, settings.mtu, 0, 0);
+ buffer_pool.set_config(config).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::Settings,
+ ["Failed to configure buffer pool {:?}", err]
+ )
+ })?;
+
+ let socket = Socket::new(element.upcast_ref(), buffer_pool, async move {
+ Ok(UdpReader::new(socket))
+ })
+ .map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to prepare socket {:?}", err]
+ )
+ })?;
+
+ *self.socket.lock().unwrap() = Some(socket);
+ element.notify("used-socket");
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+ self.src_pad_handler
+ .prepare(settings.caps, settings.retrieve_sender_address);
+ self.src_pad.prepare(&self.src_pad_handler);
+
+ gst_debug!(CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Unpreparing");
+
+ *self.socket.lock().unwrap() = None;
+ self.settings.lock().unwrap().used_socket = None;
+ element.notify("used-socket");
+
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ gst_debug!(CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Stopping");
+
+ self.task.stop();
+ self.src_pad_handler.reset();
+
+ gst_debug!(CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let socket = self.socket.lock().unwrap();
+ let socket = socket.as_ref().unwrap();
+ if socket.state() == SocketState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return Ok(());
+ }
+
+ gst_debug!(CAT, obj: element, "Starting");
+ self.start_task(element, socket);
+ gst_debug!(CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_task(&self, element: &gst::Element, socket: &Socket<UdpReader>) {
+ let socket_stream = socket
+ .start(element.get_clock(), Some(element.get_base_time()))
+ .unwrap();
+ let socket_stream = Arc::new(FutMutex::new(socket_stream));
+
+ let src_pad_handler = self.src_pad_handler.clone();
+ let pad_weak = self.src_pad.downgrade();
+ let element = element.clone();
+
+ self.task.start(move || {
+ let src_pad_handler = src_pad_handler.clone();
+ let pad_weak = pad_weak.clone();
+ let element = element.clone();
+ let socket_stream = socket_stream.clone();
+
+ async move {
+ let item = socket_stream.lock().await.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+ let (mut buffer, saddr) = match item {
+ Some(Ok((buffer, saddr))) => (buffer, saddr),
+ Some(Err(err)) => {
+ gst_error!(CAT, obj: &element, "Got error {:?}", err);
+ match err {
+ SocketError::Gst(err) => {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ }
+ SocketError::Io(err) => {
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("I/O error"),
+ ["streaming stopped, I/O error {}", err]
+ );
+ }
+ }
+ return glib::Continue(false);
+ }
+ None => {
+ gst_log!(CAT, obj: pad.gst_pad(), "SocketStream Stopped");
+ return glib::Continue(false);
+ }
+ };
+
+ if let Some(saddr) = saddr {
+ if src_pad_handler.0.state.lock().await.retrieve_sender_address {
+ let inet_addr = match saddr.ip() {
+ IpAddr::V4(ip) => gio::InetAddress::new_from_bytes(
+ gio::InetAddressBytes::V4(&ip.octets()),
+ ),
+ IpAddr::V6(ip) => gio::InetAddress::new_from_bytes(
+ gio::InetAddressBytes::V6(&ip.octets()),
+ ),
+ };
+ let inet_socket_addr =
+ &gio::InetSocketAddress::new(&inet_addr, saddr.port());
+ NetAddressMeta::add(buffer.get_mut().unwrap(), inet_socket_addr);
+ }
+ }
+
+ match src_pad_handler.push_buffer(&pad, &element, buffer).await {
+ Ok(_) => {
+ gst_log!(CAT, obj: pad.gst_pad(), "Successfully pushed buffer");
+ glib::Continue(true)
+ }
+ Err(gst::FlowError::Flushing) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "Flushing");
+ glib::Continue(false)
+ }
+ Err(gst::FlowError::Eos) => {
+ gst_debug!(CAT, obj: pad.gst_pad(), "EOS");
+ let eos = gst::Event::new_eos().build();
+ pad.push_event(eos).await;
+ glib::Continue(false)
+ }
+ Err(err) => {
+ gst_error!(CAT, obj: pad.gst_pad(), "Got error {}", err);
+ gst_element_error!(
+ element,
+ gst::StreamError::Failed,
+ ("Internal data stream error"),
+ ["streaming stopped, reason {}", err]
+ );
+ glib::Continue(false)
+ }
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ let socket = self.socket.lock().unwrap();
+ if let Some(socket) = socket.as_ref() {
+ if socket.state() == SocketState::Started {
+ gst_debug!(CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(CAT, obj: element, "Stopping Flush");
+
+ self.src_pad_handler.set_need_segment();
+ self.start_task(element, socket);
+
+ gst_debug!(CAT, obj: element, "Stopped Flush");
+ } else {
+ gst_debug!(CAT, obj: element, "Socket not available");
+ }
+ }
+
+ fn flush_start(&self, element: &gst::Element) {
+ let socket = self.socket.lock().unwrap();
+ gst_debug!(CAT, obj: element, "Starting Flush");
+
+ if let Some(socket) = socket.as_ref() {
+ socket.pause();
+ }
+
+ self.task.cancel();
+
+ gst_debug!(CAT, obj: element, "Flush Started");
+ }
+
+ fn pause(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(CAT, obj: element, "Pausing");
+
+ self.socket.lock().unwrap().as_ref().unwrap().pause();
+ self.task.pause();
+
+ gst_debug!(CAT, obj: element, "Paused");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for UdpSrc {
+ const NAME: &'static str = "RsTsUdpSrc";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing UDP source",
+ "Source/Network",
+ "Receives data over the network via UDP",
+ "Sebastian Dröge <sebastian@centricular.com>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ #[cfg(not(windows))]
+ {
+ klass.install_properties(&PROPERTIES);
+ }
+ #[cfg(windows)]
+ {
+ let properties = PROPERTIES
+ .iter()
+ .filter(|p| match *p {
+ subclass::Property("socket", ..) | subclass::Property("used-socket", ..) => {
+ false
+ }
+ _ => true,
+ })
+ .collect::<Vec<_>>();
+ klass.install_properties(properties.as_slice());
+ }
+ }
+
+ fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
+ Self {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ src_pad_handler: UdpSrcPadHandler::default(),
+ task: Task::default(),
+ socket: StdMutex::new(None),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for UdpSrc {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &PROPERTIES[id];
+
+ let mut settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("address", ..) => {
+ settings.address = value.get().expect("type checked upstream");
+ }
+ subclass::Property("port", ..) => {
+ settings.port = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("reuse", ..) => {
+ settings.reuse = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("caps", ..) => {
+ settings.caps = value.get().expect("type checked upstream");
+ }
+ subclass::Property("mtu", ..) => {
+ settings.mtu = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("socket", ..) => {
+ settings.socket = value
+ .get::<gio::Socket>()
+ .expect("type checked upstream")
+ .map(|socket| GioSocketWrapper::new(&socket));
+ }
+ subclass::Property("used-socket", ..) => {
+ unreachable!();
+ }
+ subclass::Property("context", ..) => {
+ settings.context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+ }
+ subclass::Property("context-wait", ..) => {
+ settings.context_wait = value.get_some().expect("type checked upstream");
+ }
+ subclass::Property("retrieve-sender-address", ..) => {
+ settings.retrieve_sender_address = value.get_some().expect("type checked upstream");
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+ let prop = &PROPERTIES[id];
+
+ let settings = self.settings.lock().unwrap();
+ match *prop {
+ subclass::Property("address", ..) => Ok(settings.address.to_value()),
+ subclass::Property("port", ..) => Ok(settings.port.to_value()),
+ subclass::Property("reuse", ..) => Ok(settings.reuse.to_value()),
+ subclass::Property("caps", ..) => Ok(settings.caps.to_value()),
+ subclass::Property("mtu", ..) => Ok(settings.mtu.to_value()),
+ subclass::Property("socket", ..) => Ok(settings
+ .socket
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("used-socket", ..) => Ok(settings
+ .used_socket
+ .as_ref()
+ .map(GioSocketWrapper::as_socket)
+ .to_value()),
+ subclass::Property("context", ..) => Ok(settings.context.to_value()),
+ subclass::Property("context-wait", ..) => Ok(settings.context_wait.to_value()),
+ subclass::Property("retrieve-sender-address", ..) => {
+ Ok(settings.retrieve_sender_address.to_value())
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+
+ super::set_element_flags(element, gst::ElementFlags::SOURCE);
+ }
+}
+
+impl ElementImpl for UdpSrc {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_trace!(CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ self.pause(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::ReadyToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToPlaying => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+}
+
+pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
+ gst::Element::register(
+ Some(plugin),
+ "ts-udpsrc",
+ gst::Rank::None,
+ UdpSrc::get_type(),
+ )
+}
diff --git a/generic/gst-plugin-threadshare/tests/appsrc.rs b/generic/gst-plugin-threadshare/tests/appsrc.rs
new file mode 100644
index 000000000..8610fcb47
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/appsrc.rs
@@ -0,0 +1,303 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+
+use gst_check;
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare appsrc test");
+ });
+}
+
+#[test]
+fn push() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-appsrc");
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ {
+ let appsrc = h.get_element().unwrap();
+ appsrc.set_property("caps", &caps).unwrap();
+ appsrc.set_property("do-timestamp", &true).unwrap();
+ appsrc.set_property("context", &"appsrc-push").unwrap();
+ }
+
+ h.play();
+
+ {
+ let appsrc = h.get_element().unwrap();
+ for _ in 0..3 {
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+ }
+
+ assert!(appsrc
+ .emit("end-of-stream", &[])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+ }
+
+ for _ in 0..3 {
+ let _buffer = h.pull().unwrap();
+ }
+
+ let mut n_events = 0;
+ loop {
+ use gst::EventView;
+
+ let event = h.pull_event().unwrap();
+ match event.view() {
+ EventView::StreamStart(..) => {
+ assert_eq!(n_events, 0);
+ }
+ EventView::Caps(ev) => {
+ assert_eq!(n_events, 1);
+ let event_caps = ev.get_caps();
+ assert_eq!(caps.as_ref(), event_caps);
+ }
+ EventView::Segment(..) => {
+ assert_eq!(n_events, 2);
+ }
+ EventView::Eos(..) => {
+ break;
+ }
+ _ => (),
+ }
+ n_events += 1;
+ }
+ assert!(n_events >= 2);
+}
+
+#[test]
+fn pause() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-appsrc");
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ {
+ let appsrc = h.get_element().unwrap();
+ appsrc.set_property("caps", &caps).unwrap();
+ appsrc.set_property("do-timestamp", &true).unwrap();
+ appsrc.set_property("context", &"appsrc-pause").unwrap();
+ }
+
+ h.play();
+
+ let appsrc = h.get_element().unwrap();
+
+ // Initial buffer
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::from_slice(vec![1, 2, 3, 4])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+
+ appsrc
+ .change_state(gst::StateChange::PlayingToPaused)
+ .unwrap();
+
+ // Pre-pause buffer
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::from_slice(vec![5, 6, 7])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ appsrc
+ .change_state(gst::StateChange::PlayingToPaused)
+ .unwrap();
+
+ // Buffer is queued during Paused
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::from_slice(vec![8, 9])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ appsrc
+ .change_state(gst::StateChange::PausedToPlaying)
+ .unwrap();
+
+ // Pull Pre-pause buffer
+ let _ = h.pull().unwrap();
+
+ // Pull buffer queued while Paused
+ let _ = h.pull().unwrap();
+
+ // Can push again
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+ assert!(h.try_pull().is_none());
+}
+
+#[test]
+fn flush() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-appsrc");
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ {
+ let appsrc = h.get_element().unwrap();
+ appsrc.set_property("caps", &caps).unwrap();
+ appsrc.set_property("do-timestamp", &true).unwrap();
+ appsrc.set_property("context", &"appsrc-flush").unwrap();
+ }
+
+ h.play();
+
+ let appsrc = h.get_element().unwrap();
+
+ // Initial buffer
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::from_slice(vec![1, 2, 3, 4])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+
+ // FlushStart
+ assert!(h.push_upstream_event(gst::Event::new_flush_start().build()));
+
+ // Can't push buffer while flushing
+ assert!(!appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ assert!(h.try_pull().is_none());
+
+ // FlushStop
+ assert!(h.push_upstream_event(gst::Event::new_flush_stop(true).build()));
+
+ // No buffer available due to flush
+ assert!(h.try_pull().is_none());
+
+ // Can push again
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+ assert!(h.try_pull().is_none());
+}
+
+#[test]
+fn pause_flush() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-appsrc");
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ {
+ let appsrc = h.get_element().unwrap();
+ appsrc.set_property("caps", &caps).unwrap();
+ appsrc.set_property("do-timestamp", &true).unwrap();
+ appsrc
+ .set_property("context", &"appsrc-pause_flush")
+ .unwrap();
+ }
+
+ h.play();
+
+ let appsrc = h.get_element().unwrap();
+
+ // Initial buffer
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::from_slice(vec![1, 2, 3, 4])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+
+ appsrc
+ .change_state(gst::StateChange::PlayingToPaused)
+ .unwrap();
+
+ // FlushStart
+ assert!(h.push_upstream_event(gst::Event::new_flush_start().build()));
+
+ // Can't push buffer while flushing
+ assert!(!appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ assert!(h.try_pull().is_none());
+
+ // FlushStop
+ assert!(h.push_upstream_event(gst::Event::new_flush_stop(true).build()));
+
+ appsrc
+ .change_state(gst::StateChange::PausedToPlaying)
+ .unwrap();
+
+ // No buffer available due to flush
+ assert!(h.try_pull().is_none());
+
+ // Can push again
+ assert!(appsrc
+ .emit("push-buffer", &[&gst::Buffer::new()])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ let _ = h.pull().unwrap();
+ assert!(h.try_pull().is_none());
+}
diff --git a/generic/gst-plugin-threadshare/tests/inputselector.rs b/generic/gst-plugin-threadshare/tests/inputselector.rs
new file mode 100644
index 000000000..965c266c1
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/inputselector.rs
@@ -0,0 +1,108 @@
+// Copyright (C) 2020 Mathieu Duponchelle <mathieu@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib;
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+use gst_check;
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare inputselector test");
+ });
+}
+
+#[test]
+fn test_active_pad() {
+ init();
+
+ let is = gst::ElementFactory::make("ts-input-selector", None).unwrap();
+
+ let mut h1 = gst_check::Harness::new_with_element(&is, Some("sink_%u"), Some("src"));
+ let mut h2 = gst_check::Harness::new_with_element(&is, Some("sink_%u"), None);
+
+ let active_pad = is
+ .get_property("active-pad")
+ .unwrap()
+ .get::<gst::Pad>()
+ .unwrap();
+ assert_eq!(active_pad, h1.get_srcpad().unwrap().get_peer());
+
+ is.set_property("active-pad", &h2.get_srcpad().unwrap().get_peer())
+ .unwrap();
+ let active_pad = is
+ .get_property("active-pad")
+ .unwrap()
+ .get::<gst::Pad>()
+ .unwrap();
+ assert_eq!(active_pad, h2.get_srcpad().unwrap().get_peer());
+
+ h1.set_src_caps_str("foo/bar");
+ h2.set_src_caps_str("foo/bar");
+
+ h1.play();
+
+ /* Push buffer on inactive pad, we should not receive anything */
+ let buf = gst::Buffer::new();
+ assert_eq!(h1.push(buf), Ok(gst::FlowSuccess::Ok));
+ assert_eq!(h1.buffers_received(), 0);
+
+ /* Buffers pushed on the active pad should be received */
+ let buf = gst::Buffer::new();
+ assert_eq!(h2.push(buf), Ok(gst::FlowSuccess::Ok));
+ assert_eq!(h1.buffers_received(), 1);
+
+ assert_eq!(h1.events_received(), 3);
+
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::StreamStart);
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::Caps);
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::Segment);
+
+ /* Push another buffer on the active pad, there should be no new events */
+ let buf = gst::Buffer::new();
+ assert_eq!(h2.push(buf), Ok(gst::FlowSuccess::Ok));
+ assert_eq!(h1.buffers_received(), 2);
+ assert_eq!(h1.events_received(), 3);
+
+ /* Switch the active pad and push a buffer, we should receive stream-start, segment and caps
+ * again */
+ let buf = gst::Buffer::new();
+ is.set_property("active-pad", &h1.get_srcpad().unwrap().get_peer())
+ .unwrap();
+ assert_eq!(h1.push(buf), Ok(gst::FlowSuccess::Ok));
+ assert_eq!(h1.buffers_received(), 3);
+ assert_eq!(h1.events_received(), 6);
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::StreamStart);
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::Caps);
+ let event = h1.pull_event().unwrap();
+ assert_eq!(event.get_type(), gst::EventType::Segment);
+
+ let _ = is.set_state(gst::State::Null);
+}
diff --git a/generic/gst-plugin-threadshare/tests/jitterbuffer.rs b/generic/gst-plugin-threadshare/tests/jitterbuffer.rs
new file mode 100644
index 000000000..cdc227672
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/jitterbuffer.rs
@@ -0,0 +1,172 @@
+// Copyright (C) 2020 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use gst;
+use gst::gst_debug;
+use gst::prelude::*;
+
+use lazy_static::lazy_static;
+
+use std::sync::mpsc;
+
+use gstthreadshare;
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-test",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing test"),
+ );
+}
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare jitterbuffer test");
+ });
+}
+
+#[test]
+fn jb_pipeline() {
+ init();
+
+ const CONTEXT_WAIT: u32 = 20;
+ const LATENCY: u32 = 20;
+ const BUFFER_NB: i32 = 3;
+
+ let pipeline = gst::Pipeline::new(None);
+
+ let src = gst::ElementFactory::make("audiotestsrc", Some("audiotestsrc")).unwrap();
+ src.set_property("is-live", &true).unwrap();
+ src.set_property("num-buffers", &BUFFER_NB).unwrap();
+
+ let enc = gst::ElementFactory::make("alawenc", Some("alawenc")).unwrap();
+ let pay = gst::ElementFactory::make("rtppcmapay", Some("rtppcmapay")).unwrap();
+
+ let jb = gst::ElementFactory::make("ts-jitterbuffer", Some("ts-jitterbuffer")).unwrap();
+ jb.set_property("context", &"jb_pipeline").unwrap();
+ jb.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+ jb.set_property("latency", &LATENCY).unwrap();
+
+ let depay = gst::ElementFactory::make("rtppcmadepay", Some("rtppcmadepay")).unwrap();
+ let dec = gst::ElementFactory::make("alawdec", Some("alawdec")).unwrap();
+
+ let sink = gst::ElementFactory::make("appsink", Some("appsink")).unwrap();
+ sink.set_property("sync", &false).unwrap();
+ sink.set_property("async", &false).unwrap();
+ sink.set_property("emit-signals", &true).unwrap();
+
+ pipeline
+ .add_many(&[&src, &enc, &pay, &jb, &depay, &dec, &sink])
+ .unwrap();
+ gst::Element::link_many(&[&src, &enc, &pay, &jb, &depay, &dec, &sink]).unwrap();
+
+ let appsink = sink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let (sender, receiver) = mpsc::channel();
+ appsink.connect_new_sample(move |appsink| {
+ let _sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ sender.send(()).unwrap();
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "jb_pipeline: waiting for {} buffers", BUFFER_NB);
+ for idx in 0..BUFFER_NB {
+ receiver.recv().unwrap();
+ gst_debug!(CAT, "jb_pipeline: received buffer #{}", idx);
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn jb_ts_pipeline() {
+ init();
+
+ const CONTEXT_WAIT: u32 = 20;
+ const LATENCY: u32 = 20;
+ const BUFFER_NB: i32 = 3;
+
+ let pipeline = gst::Pipeline::new(None);
+
+ let src = gst::ElementFactory::make("audiotestsrc", Some("audiotestsrc")).unwrap();
+ src.set_property("is-live", &true).unwrap();
+ src.set_property("num-buffers", &BUFFER_NB).unwrap();
+
+ let queue = gst::ElementFactory::make("ts-queue", Some("ts-queue")).unwrap();
+ queue
+ .set_property("context", &"jb_ts_pipeline_queue")
+ .unwrap();
+ queue.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+
+ let enc = gst::ElementFactory::make("alawenc", Some("alawenc")).unwrap();
+ let pay = gst::ElementFactory::make("rtppcmapay", Some("rtppcmapay")).unwrap();
+
+ let jb = gst::ElementFactory::make("ts-jitterbuffer", Some("ts-jitterbuffer")).unwrap();
+ jb.set_property("context", &"jb_ts_pipeline").unwrap();
+ jb.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+ jb.set_property("latency", &LATENCY).unwrap();
+
+ let depay = gst::ElementFactory::make("rtppcmadepay", Some("rtppcmadepay")).unwrap();
+ let dec = gst::ElementFactory::make("alawdec", Some("alawdec")).unwrap();
+
+ let sink = gst::ElementFactory::make("appsink", Some("appsink")).unwrap();
+ sink.set_property("sync", &false).unwrap();
+ sink.set_property("async", &false).unwrap();
+ sink.set_property("emit-signals", &true).unwrap();
+
+ pipeline
+ .add_many(&[&src, &queue, &enc, &pay, &jb, &depay, &dec, &sink])
+ .unwrap();
+ gst::Element::link_many(&[&src, &queue, &enc, &pay, &jb, &depay, &dec, &sink]).unwrap();
+
+ let appsink = sink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let (sender, receiver) = mpsc::channel();
+ appsink.connect_new_sample(move |appsink| {
+ let _sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ sender.send(()).unwrap();
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "jb_ts_pipeline: waiting for {} buffers", BUFFER_NB);
+ for idx in 0..BUFFER_NB {
+ receiver.recv().unwrap();
+ gst_debug!(CAT, "jb_ts_pipeline: received buffer #{}", idx);
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
diff --git a/generic/gst-plugin-threadshare/tests/pad.rs b/generic/gst-plugin-threadshare/tests/pad.rs
new file mode 100644
index 000000000..8bf680c04
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/pad.rs
@@ -0,0 +1,1322 @@
+// Copyright (C) 2019-2020 François Laignel <fengalin@free.fr>
+// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use futures::channel::mpsc;
+use futures::future::BoxFuture;
+use futures::lock::Mutex as FutMutex;
+use futures::prelude::*;
+
+use glib;
+use glib::GBoxed;
+use glib::{glib_object_impl, glib_object_subclass};
+
+use gst;
+use gst::prelude::*;
+use gst::subclass::prelude::*;
+use gst::EventView;
+use gst::{gst_debug, gst_error_msg, gst_info, gst_log};
+
+use lazy_static::lazy_static;
+
+use std::boxed::Box;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use std::sync::Mutex as StdMutex;
+
+use gstthreadshare::runtime::prelude::*;
+use gstthreadshare::runtime::{Context, PadSink, PadSinkRef, PadSrc, PadSrcRef, Task};
+
+const DEFAULT_CONTEXT: &str = "";
+const THROTTLING_DURATION: u32 = 2;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare pad test");
+ });
+}
+
+// Src
+
+static SRC_PROPERTIES: [glib::subclass::Property; 1] =
+ [glib::subclass::Property("context", |name| {
+ glib::ParamSpec::string(
+ name,
+ "Context",
+ "Context name to share threads with",
+ Some(DEFAULT_CONTEXT),
+ glib::ParamFlags::READWRITE,
+ )
+ })];
+
+#[derive(Clone, Debug, Default)]
+struct Settings {
+ context: String,
+}
+
+lazy_static! {
+ static ref SRC_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-element-src-test",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing Test Src Element"),
+ );
+}
+
+#[derive(Clone, Debug)]
+struct PadSrcTestHandler;
+
+impl PadSrcTestHandler {
+ async fn push_item(pad: PadSrcRef<'_>, item: Item) -> Result<gst::FlowSuccess, gst::FlowError> {
+ gst_debug!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", item);
+
+ match item {
+ Item::Event(event) => {
+ pad.push_event(event).await;
+
+ Ok(gst::FlowSuccess::Ok)
+ }
+ Item::Buffer(buffer) => pad.push(buffer).await,
+ Item::BufferList(list) => pad.push_list(list).await,
+ }
+ }
+}
+
+impl PadSrcHandler for PadSrcTestHandler {
+ type ElementImpl = ElementSrcTest;
+
+ fn src_event(
+ &self,
+ pad: &PadSrcRef,
+ elem_src_test: &ElementSrcTest,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", event);
+
+ let ret = match event.view() {
+ EventView::FlushStart(..) => {
+ elem_src_test.flush_start(element);
+ true
+ }
+ EventView::Qos(..) | EventView::Reconfigure(..) | EventView::Latency(..) => true,
+ EventView::FlushStop(..) => {
+ elem_src_test.flush_stop(&element);
+ true
+ }
+ _ => false,
+ };
+
+ if ret {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Handled {:?}", event);
+ } else {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "Didn't handle {:?}", event);
+ }
+
+ ret
+ }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+enum ElementSrcTestState {
+ Paused,
+ RejectItems,
+ Started,
+}
+
+#[derive(Debug)]
+struct ElementSrcTest {
+ src_pad: PadSrc,
+ task: Task,
+ state: StdMutex<ElementSrcTestState>,
+ sender: StdMutex<Option<mpsc::Sender<Item>>>,
+ receiver: StdMutex<Option<Arc<FutMutex<mpsc::Receiver<Item>>>>>,
+ settings: StdMutex<Settings>,
+}
+
+impl ElementSrcTest {
+ fn try_push(&self, item: Item) -> Result<(), Item> {
+ let state = self.state.lock().unwrap();
+ if *state == ElementSrcTestState::RejectItems {
+ gst_debug!(SRC_CAT, "ElementSrcTest rejecting item due to pad state");
+
+ return Err(item);
+ }
+
+ match self.sender.lock().unwrap().as_mut() {
+ Some(sender) => sender
+ .try_send(item)
+ .map_err(mpsc::TrySendError::into_inner),
+ None => Err(item),
+ }
+ }
+
+ fn prepare(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> {
+ gst_debug!(SRC_CAT, obj: element, "Preparing");
+
+ let settings = self.settings.lock().unwrap().clone();
+ let context = Context::acquire(&settings.context, THROTTLING_DURATION).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Failed to acquire Context: {}", err]
+ )
+ })?;
+
+ self.task.prepare(context).map_err(|err| {
+ gst_error_msg!(
+ gst::ResourceError::OpenRead,
+ ["Error preparing Task: {:?}", err]
+ )
+ })?;
+ self.src_pad.prepare(&PadSrcTestHandler);
+
+ let (sender, receiver) = mpsc::channel(1);
+ *self.sender.lock().unwrap() = Some(sender);
+ *self.receiver.lock().unwrap() = Some(Arc::new(FutMutex::new(receiver)));
+
+ gst_debug!(SRC_CAT, obj: element, "Prepared");
+
+ Ok(())
+ }
+
+ fn unprepare(&self, element: &gst::Element) -> Result<(), ()> {
+ gst_debug!(SRC_CAT, obj: element, "Unpreparing");
+
+ self.task.unprepare().unwrap();
+ self.src_pad.unprepare();
+
+ *self.sender.lock().unwrap() = None;
+ *self.receiver.lock().unwrap() = None;
+
+ gst_debug!(SRC_CAT, obj: element, "Unprepared");
+
+ Ok(())
+ }
+
+ fn stop(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(SRC_CAT, obj: element, "Stopping");
+
+ self.flush(element);
+ *state = ElementSrcTestState::RejectItems;
+
+ gst_debug!(SRC_CAT, obj: element, "Stopped");
+
+ Ok(())
+ }
+
+ fn flush(&self, element: &gst::Element) {
+ gst_debug!(SRC_CAT, obj: element, "Flushing");
+
+ self.task.stop();
+
+ let receiver = self.receiver.lock().unwrap();
+ let mut receiver = receiver
+ .as_ref()
+ .unwrap()
+ .try_lock()
+ .expect("receiver locked elsewhere");
+
+ // Purge the channel
+ loop {
+ match receiver.try_next() {
+ Ok(Some(_item)) => {
+ gst_debug!(SRC_CAT, obj: element, "Dropping pending item");
+ }
+ Err(_) => {
+ gst_debug!(SRC_CAT, obj: element, "No more pending item");
+ break;
+ }
+ Ok(None) => {
+ panic!("Channel sender dropped");
+ }
+ }
+ }
+
+ gst_debug!(SRC_CAT, obj: element, "Flushed");
+ }
+
+ fn start(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ if *state == ElementSrcTestState::Started {
+ gst_debug!(SRC_CAT, obj: element, "Already started");
+ return Err(());
+ }
+
+ gst_debug!(SRC_CAT, obj: element, "Starting");
+
+ self.start_task();
+ *state = ElementSrcTestState::Started;
+
+ gst_debug!(SRC_CAT, obj: element, "Started");
+
+ Ok(())
+ }
+
+ fn start_task(&self) {
+ let pad_weak = self.src_pad.downgrade();
+ let receiver = Arc::clone(self.receiver.lock().unwrap().as_ref().expect("No receiver"));
+
+ self.task.start(move || {
+ let pad_weak = pad_weak.clone();
+ let receiver = Arc::clone(&receiver);
+
+ async move {
+ let item = receiver.lock().await.next().await;
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+
+ let item = match item {
+ Some(item) => item,
+ None => {
+ gst_log!(SRC_CAT, obj: pad.gst_pad(), "SrcPad channel aborted");
+ return glib::Continue(false);
+ }
+ };
+
+ let pad = pad_weak.upgrade().expect("PadSrc no longer exists");
+ match PadSrcTestHandler::push_item(pad, item).await {
+ Ok(_) => glib::Continue(true),
+ Err(gst::FlowError::Flushing) => glib::Continue(false),
+ Err(err) => panic!("Got error {:?}", err),
+ }
+ }
+ });
+ }
+
+ fn flush_stop(&self, element: &gst::Element) {
+ let mut state = self.state.lock().unwrap();
+ if *state == ElementSrcTestState::Started {
+ gst_debug!(SRC_CAT, obj: element, "Already started");
+ return;
+ }
+
+ gst_debug!(SRC_CAT, obj: element, "Stopping Flush");
+
+ self.flush(element);
+ self.start_task();
+ *state = ElementSrcTestState::Started;
+
+ gst_debug!(SRC_CAT, obj: element, "Stopped Flush");
+ }
+
+ fn flush_start(&self, element: &gst::Element) {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(SRC_CAT, obj: element, "Starting Flush");
+
+ self.task.cancel();
+ *state = ElementSrcTestState::RejectItems;
+
+ gst_debug!(SRC_CAT, obj: element, "Flush Started");
+ }
+
+ fn pause(&self, element: &gst::Element) -> Result<(), ()> {
+ let mut state = self.state.lock().unwrap();
+ gst_debug!(SRC_CAT, obj: element, "Pausing");
+
+ self.task.pause();
+ *state = ElementSrcTestState::Paused;
+
+ gst_debug!(SRC_CAT, obj: element, "Paused");
+
+ Ok(())
+ }
+}
+
+impl ObjectSubclass for ElementSrcTest {
+ const NAME: &'static str = "TsElementSrcTest";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = glib::subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut glib::subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing Test Src Element",
+ "Generic",
+ "Src Element for Pad Src Test",
+ "François Laignel <fengalin@free.fr>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let src_pad_template = gst::PadTemplate::new(
+ "src",
+ gst::PadDirection::Src,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(src_pad_template);
+
+ klass.install_properties(&SRC_PROPERTIES);
+ }
+
+ fn new_with_class(klass: &glib::subclass::simple::ClassStruct<Self>) -> Self {
+ ElementSrcTest {
+ src_pad: PadSrc::new(gst::Pad::new_from_template(
+ &klass.get_pad_template("src").unwrap(),
+ Some("src"),
+ )),
+ task: Task::default(),
+ state: StdMutex::new(ElementSrcTestState::RejectItems),
+ sender: StdMutex::new(None),
+ receiver: StdMutex::new(None),
+ settings: StdMutex::new(Settings::default()),
+ }
+ }
+}
+
+impl ObjectImpl for ElementSrcTest {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &SRC_PROPERTIES[id];
+
+ match *prop {
+ glib::subclass::Property("context", ..) => {
+ let context = value
+ .get()
+ .expect("type checked upstream")
+ .unwrap_or_else(|| "".into());
+
+ self.settings.lock().unwrap().context = context;
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.src_pad.gst_pad()).unwrap();
+ }
+}
+
+impl ElementImpl for ElementSrcTest {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_log!(SRC_CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.prepare(element).map_err(|err| {
+ element.post_error_message(&err);
+ gst::StateChangeError
+ })?;
+ }
+ gst::StateChange::PlayingToPaused => {
+ self.pause(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToNull => {
+ self.unprepare(element).map_err(|_| gst::StateChangeError)?;
+ }
+ _ => (),
+ }
+
+ let mut success = self.parent_change_state(element, transition)?;
+
+ match transition {
+ gst::StateChange::PausedToReady => {
+ self.stop(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::PausedToPlaying => {
+ self.start(element).map_err(|_| gst::StateChangeError)?;
+ }
+ gst::StateChange::ReadyToPaused | gst::StateChange::PlayingToPaused => {
+ success = gst::StateChangeSuccess::NoPreroll;
+ }
+ _ => (),
+ }
+
+ Ok(success)
+ }
+
+ fn send_event(&self, element: &gst::Element, event: gst::Event) -> bool {
+ match event.view() {
+ EventView::FlushStart(..) => {
+ self.flush_start(element);
+ }
+ EventView::FlushStop(..) => {
+ self.flush_stop(element);
+ }
+ _ => (),
+ }
+
+ if !event.is_serialized() {
+ self.src_pad.gst_pad().push_event(event)
+ } else {
+ self.try_push(Item::Event(event)).is_ok()
+ }
+ }
+}
+
+// Sink
+
+#[derive(Debug)]
+enum Item {
+ Buffer(gst::Buffer),
+ BufferList(gst::BufferList),
+ Event(gst::Event),
+}
+
+#[derive(Clone, Debug, GBoxed)]
+#[gboxed(type_name = "TsTestItemSender")]
+struct ItemSender {
+ sender: mpsc::Sender<Item>,
+}
+
+static SINK_PROPERTIES: [glib::subclass::Property; 1] =
+ [glib::subclass::Property("sender", |name| {
+ glib::ParamSpec::boxed(
+ name,
+ "Sender",
+ "Channel sender to forward the incoming items to",
+ ItemSender::get_type(),
+ glib::ParamFlags::WRITABLE,
+ )
+ })];
+
+#[derive(Clone, Debug, Default)]
+struct PadSinkTestHandler;
+
+impl PadSinkHandler for PadSinkTestHandler {
+ type ElementImpl = ElementSinkTest;
+
+ fn sink_chain(
+ &self,
+ _pad: &PadSinkRef,
+ _elem_sink_test: &ElementSinkTest,
+ element: &gst::Element,
+ buffer: gst::Buffer,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let element = element.clone();
+ async move {
+ let elem_sink_test = ElementSinkTest::from_instance(&element);
+ elem_sink_test
+ .forward_item(&element, Item::Buffer(buffer))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_chain_list(
+ &self,
+ _pad: &PadSinkRef,
+ _elem_sink_test: &ElementSinkTest,
+ element: &gst::Element,
+ list: gst::BufferList,
+ ) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
+ let element = element.clone();
+ async move {
+ let elem_sink_test = ElementSinkTest::from_instance(&element);
+ elem_sink_test
+ .forward_item(&element, Item::BufferList(list))
+ .await
+ }
+ .boxed()
+ }
+
+ fn sink_event(
+ &self,
+ pad: &PadSinkRef,
+ elem_sink_test: &ElementSinkTest,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> bool {
+ gst_debug!(SINK_CAT, obj: pad.gst_pad(), "Handling non-serialized {:?}", event);
+
+ match event.view() {
+ EventView::FlushStart(..) => {
+ elem_sink_test.stop(&element);
+ true
+ }
+ _ => false,
+ }
+ }
+
+ fn sink_event_serialized(
+ &self,
+ pad: &PadSinkRef,
+ _elem_sink_test: &ElementSinkTest,
+ element: &gst::Element,
+ event: gst::Event,
+ ) -> BoxFuture<'static, bool> {
+ gst_log!(SINK_CAT, obj: pad.gst_pad(), "Handling serialized {:?}", event);
+
+ let element = element.clone();
+ async move {
+ let elem_sink_test = ElementSinkTest::from_instance(&element);
+
+ if let EventView::FlushStop(..) = event.view() {
+ elem_sink_test.start(&element);
+ }
+
+ elem_sink_test
+ .forward_item(&element, Item::Event(event))
+ .await
+ .is_ok()
+ }
+ .boxed()
+ }
+}
+
+#[derive(Debug)]
+struct ElementSinkTest {
+ sink_pad: PadSink,
+ flushing: AtomicBool,
+ sender: FutMutex<Option<mpsc::Sender<Item>>>,
+}
+
+impl ElementSinkTest {
+ async fn forward_item(
+ &self,
+ element: &gst::Element,
+ item: Item,
+ ) -> Result<gst::FlowSuccess, gst::FlowError> {
+ if !self.flushing.load(Ordering::SeqCst) {
+ gst_debug!(SINK_CAT, obj: element, "Fowarding {:?}", item);
+ self.sender
+ .lock()
+ .await
+ .as_mut()
+ .expect("Item Sender not set")
+ .send(item)
+ .await
+ .map(|_| gst::FlowSuccess::Ok)
+ .map_err(|_| gst::FlowError::Error)
+ } else {
+ gst_debug!(
+ SINK_CAT,
+ obj: element,
+ "Not fowarding {:?} due to flushing",
+ item
+ );
+ Err(gst::FlowError::Flushing)
+ }
+ }
+
+ fn start(&self, element: &gst::Element) {
+ gst_debug!(SINK_CAT, obj: element, "Starting");
+ self.flushing.store(false, Ordering::SeqCst);
+ gst_debug!(SINK_CAT, obj: element, "Started");
+ }
+
+ fn stop(&self, element: &gst::Element) {
+ gst_debug!(SINK_CAT, obj: element, "Stopping");
+ self.flushing.store(true, Ordering::SeqCst);
+ gst_debug!(SINK_CAT, obj: element, "Stopped");
+ }
+}
+
+impl ElementSinkTest {
+ fn push_flush_start(&self, element: &gst::Element) {
+ gst_debug!(SINK_CAT, obj: element, "Pushing FlushStart");
+ self.sink_pad
+ .gst_pad()
+ .push_event(gst::Event::new_flush_start().build());
+ gst_debug!(SINK_CAT, obj: element, "FlushStart pushed");
+ }
+
+ fn push_flush_stop(&self, element: &gst::Element) {
+ gst_debug!(SINK_CAT, obj: element, "Pushing FlushStop");
+ self.sink_pad
+ .gst_pad()
+ .push_event(gst::Event::new_flush_stop(true).build());
+ gst_debug!(SINK_CAT, obj: element, "FlushStop pushed");
+ }
+}
+
+lazy_static! {
+ static ref SINK_CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-element-sink-test",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing Test Sink Element"),
+ );
+}
+
+impl ObjectSubclass for ElementSinkTest {
+ const NAME: &'static str = "TsElementSinkTest";
+ type ParentType = gst::Element;
+ type Instance = gst::subclass::ElementInstanceStruct<Self>;
+ type Class = glib::subclass::simple::ClassStruct<Self>;
+
+ glib_object_subclass!();
+
+ fn class_init(klass: &mut glib::subclass::simple::ClassStruct<Self>) {
+ klass.set_metadata(
+ "Thread-sharing Test Sink Element",
+ "Generic",
+ "Sink Element for Pad Test",
+ "François Laignel <fengalin@free.fr>",
+ );
+
+ let caps = gst::Caps::new_any();
+ let sink_pad_template = gst::PadTemplate::new(
+ "sink",
+ gst::PadDirection::Sink,
+ gst::PadPresence::Always,
+ &caps,
+ )
+ .unwrap();
+ klass.add_pad_template(sink_pad_template);
+
+ klass.install_properties(&SINK_PROPERTIES);
+ }
+
+ fn new_with_class(klass: &glib::subclass::simple::ClassStruct<Self>) -> Self {
+ let templ = klass.get_pad_template("sink").unwrap();
+ let gst_pad = gst::Pad::new_from_template(&templ, Some("sink"));
+
+ ElementSinkTest {
+ sink_pad: PadSink::new(gst_pad),
+ flushing: AtomicBool::new(true),
+ sender: FutMutex::new(None),
+ }
+ }
+}
+
+impl ObjectImpl for ElementSinkTest {
+ glib_object_impl!();
+
+ fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+ let prop = &SINK_PROPERTIES[id];
+
+ match *prop {
+ glib::subclass::Property("sender", ..) => {
+ let ItemSender { sender } = value
+ .get::<&ItemSender>()
+ .expect("type checked upstream")
+ .expect("ItemSender not found")
+ .clone();
+ *futures::executor::block_on(self.sender.lock()) = Some(sender);
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+
+ let element = obj.downcast_ref::<gst::Element>().unwrap();
+ element.add_pad(self.sink_pad.gst_pad()).unwrap();
+ }
+}
+
+impl ElementImpl for ElementSinkTest {
+ fn change_state(
+ &self,
+ element: &gst::Element,
+ transition: gst::StateChange,
+ ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
+ gst_log!(SINK_CAT, obj: element, "Changing state {:?}", transition);
+
+ match transition {
+ gst::StateChange::NullToReady => {
+ self.sink_pad.prepare(&PadSinkTestHandler::default());
+ }
+ gst::StateChange::PausedToReady => {
+ self.stop(element);
+ }
+ gst::StateChange::ReadyToNull => {
+ self.sink_pad.unprepare();
+ }
+ _ => (),
+ }
+
+ let success = self.parent_change_state(element, transition)?;
+
+ if transition == gst::StateChange::ReadyToPaused {
+ self.start(element);
+ }
+
+ Ok(success)
+ }
+}
+
+fn setup(
+ context_name: &str,
+ mut middle_element_1: Option<gst::Element>,
+ mut middle_element_2: Option<gst::Element>,
+) -> (
+ gst::Pipeline,
+ gst::Element,
+ gst::Element,
+ mpsc::Receiver<Item>,
+) {
+ init();
+
+ let pipeline = gst::Pipeline::new(None);
+
+ // Src
+ let src_element = glib::Object::new(ElementSrcTest::get_type(), &[])
+ .unwrap()
+ .downcast::<gst::Element>()
+ .unwrap();
+ src_element.set_property("context", &context_name).unwrap();
+ pipeline.add(&src_element).unwrap();
+
+ let mut last_element = src_element.clone();
+
+ if let Some(middle_element) = middle_element_1.take() {
+ pipeline.add(&middle_element).unwrap();
+ last_element.link(&middle_element).unwrap();
+ last_element = middle_element;
+ }
+
+ if let Some(middle_element) = middle_element_2.take() {
+ // Don't link the 2 middle elements: this is used for ts-proxy
+ pipeline.add(&middle_element).unwrap();
+ last_element = middle_element;
+ }
+
+ // Sink
+ let sink_element = glib::Object::new(ElementSinkTest::get_type(), &[])
+ .unwrap()
+ .downcast::<gst::Element>()
+ .unwrap();
+ pipeline.add(&sink_element).unwrap();
+ last_element.link(&sink_element).unwrap();
+
+ let (sender, receiver) = mpsc::channel::<Item>(10);
+ sink_element
+ .set_property("sender", &ItemSender { sender })
+ .unwrap();
+
+ (pipeline, src_element, sink_element, receiver)
+}
+
+fn nominal_scenario(
+ scenario_name: &str,
+ pipeline: gst::Pipeline,
+ src_element: gst::Element,
+ mut receiver: mpsc::Receiver<Item>,
+) {
+ let elem_src_test = ElementSrcTest::from_instance(&src_element);
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ // Initial events
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(scenario_name)
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![1, 2, 3, 4])))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // BufferList
+ let mut list = gst::BufferList::new();
+ list.get_mut()
+ .unwrap()
+ .add(gst::Buffer::from_slice(vec![1, 2, 3, 4]));
+ elem_src_test.try_push(Item::BufferList(list)).unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::BufferList(_) => (),
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Pause the Pad task
+ pipeline.set_state(gst::State::Paused).unwrap();
+
+ // Item accepted, but not processed before switching to Playing again
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![5, 6, 7])))
+ .unwrap();
+
+ // Nothing forwarded
+ receiver.try_next().unwrap_err();
+
+ // Switch back the Pad task to Started
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![5, 6, 7].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Flush
+ src_element.send_event(gst::Event::new_flush_start().build());
+ src_element.send_event(gst::Event::new_flush_stop(true).build());
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::FlushStop(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![8, 9])))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![8, 9].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // EOS
+ elem_src_test
+ .try_push(Item::Event(gst::Event::new_eos().build()))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Eos(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ pipeline.set_state(gst::State::Ready).unwrap();
+
+ // Receiver was dropped when stopping => can't send anymore
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(&format!("{}_past_stop", scenario_name))
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap_err();
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn src_sink_nominal() {
+ let name = "src_sink_nominal";
+
+ let (pipeline, src_element, _sink_element, receiver) = setup(&name, None, None);
+
+ nominal_scenario(&name, pipeline, src_element, receiver);
+}
+
+#[test]
+fn src_tsqueue_sink_nominal() {
+ init();
+
+ let name = "src_tsqueue_sink";
+
+ let ts_queue = gst::ElementFactory::make("ts-queue", Some("ts-queue")).unwrap();
+ ts_queue
+ .set_property("context", &format!("{}_queue", name))
+ .unwrap();
+ ts_queue
+ .set_property("context-wait", &THROTTLING_DURATION)
+ .unwrap();
+
+ let (pipeline, src_element, _sink_element, receiver) = setup(name, Some(ts_queue), None);
+
+ nominal_scenario(&name, pipeline, src_element, receiver);
+}
+
+#[test]
+fn src_queue_sink_nominal() {
+ init();
+
+ let name = "src_queue_sink";
+
+ let queue = gst::ElementFactory::make("queue", Some("queue")).unwrap();
+ let (pipeline, src_element, _sink_element, receiver) = setup(name, Some(queue), None);
+
+ nominal_scenario(&name, pipeline, src_element, receiver);
+}
+
+#[test]
+fn src_tsproxy_sink_nominal() {
+ init();
+
+ let name = "src_tsproxy_sink";
+
+ let ts_proxy_sink = gst::ElementFactory::make("ts-proxysink", Some("ts-proxysink")).unwrap();
+ ts_proxy_sink
+ .set_property("proxy-context", &format!("{}_proxy_context", name))
+ .unwrap();
+
+ let ts_proxy_src = gst::ElementFactory::make("ts-proxysrc", Some("ts-proxysrc")).unwrap();
+ ts_proxy_src
+ .set_property("proxy-context", &format!("{}_proxy_context", name))
+ .unwrap();
+ ts_proxy_src
+ .set_property("context", &format!("{}_context", name))
+ .unwrap();
+ ts_proxy_src
+ .set_property("context-wait", &THROTTLING_DURATION)
+ .unwrap();
+
+ let (pipeline, src_element, _sink_element, receiver) =
+ setup(name, Some(ts_proxy_sink), Some(ts_proxy_src));
+
+ nominal_scenario(&name, pipeline, src_element, receiver);
+}
+
+#[test]
+fn start_pause_start() {
+ init();
+
+ let scenario_name = "start_pause_start";
+
+ let (pipeline, src_element, _sink_element, mut receiver) = setup(&scenario_name, None, None);
+
+ let elem_src_test = ElementSrcTest::from_instance(&src_element);
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ // Initial events
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(scenario_name)
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![1, 2, 3, 4])))
+ .unwrap();
+
+ pipeline.set_state(gst::State::Paused).unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![5, 6, 7])))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![5, 6, 7].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Nothing else forwarded
+ receiver.try_next().unwrap_err();
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn start_stop_start() {
+ init();
+
+ let scenario_name = "start_stop_start";
+
+ let (pipeline, src_element, _sink_element, mut receiver) = setup(&scenario_name, None, None);
+
+ let elem_src_test = ElementSrcTest::from_instance(&src_element);
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ // Initial events
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(&format!("{}-after_stop", scenario_name))
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![1, 2, 3, 4])))
+ .unwrap();
+
+ pipeline.set_state(gst::State::Ready).unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ // Initial events again
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(scenario_name)
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(_buffer) => {
+ gst_info!(
+ SRC_CAT,
+ "{}: initial buffer went through, don't expect any pending item to be dropped",
+ scenario_name
+ );
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+ }
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![5, 6, 7])))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![5, 6, 7].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn start_flush() {
+ init();
+
+ let scenario_name = "start_flush";
+
+ let (pipeline, src_element, sink_element, mut receiver) = setup(&scenario_name, None, None);
+
+ let elem_src_test = ElementSrcTest::from_instance(&src_element);
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ // Initial events
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_stream_start(&format!("{}-after_stop", scenario_name))
+ .group_id(gst::GroupId::next())
+ .build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::StreamStart(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![1, 2, 3, 4])))
+ .unwrap();
+
+ let elem_sink_test = ElementSinkTest::from_instance(&sink_element);
+
+ elem_sink_test.push_flush_start(&sink_element);
+
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![5, 6, 7])))
+ .unwrap_err();
+
+ elem_sink_test.push_flush_stop(&sink_element);
+
+ elem_src_test
+ .try_push(Item::Event(
+ gst::Event::new_segment(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
+ ))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ Item::Buffer(buffer) => {
+ // In some cases, the first Buffer might be processed before FlushStart
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![1, 2, 3, 4].as_slice());
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Event(event) => match event.view() {
+ EventView::Segment(_) => (),
+ other => panic!("Unexpected event {:?}", other),
+ },
+ other => panic!("Unexpected item {:?}", other),
+ }
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ // Post flush buffer
+ elem_src_test
+ .try_push(Item::Buffer(gst::Buffer::from_slice(vec![8, 9])))
+ .unwrap();
+
+ match futures::executor::block_on(receiver.next()).unwrap() {
+ Item::Buffer(buffer) => {
+ let data = buffer.map_readable().unwrap();
+ assert_eq!(data.as_slice(), vec![8, 9].as_slice());
+ }
+ other => panic!("Unexpected item {:?}", other),
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
diff --git a/generic/gst-plugin-threadshare/tests/pipeline.rs b/generic/gst-plugin-threadshare/tests/pipeline.rs
new file mode 100644
index 000000000..61a7851aa
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/pipeline.rs
@@ -0,0 +1,627 @@
+// Copyright (C) 2019 François Laignel <fengalin@free.fr>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use gst;
+use gst::prelude::*;
+use gst::{gst_debug, gst_error};
+
+use lazy_static::lazy_static;
+
+use std::sync::mpsc;
+
+use gstthreadshare;
+
+lazy_static! {
+ static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
+ "ts-test",
+ gst::DebugColorFlags::empty(),
+ Some("Thread-sharing test"),
+ );
+}
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare pipeline test");
+ });
+}
+
+#[test]
+fn multiple_contexts_queue() {
+ use std::net;
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ use std::sync::mpsc;
+
+ init();
+
+ const CONTEXT_NB: u32 = 2;
+ const SRC_NB: u16 = 4;
+ const CONTEXT_WAIT: u32 = 1;
+ const BUFFER_NB: u32 = 3;
+ const FIRST_PORT: u16 = 40000;
+
+ let l = glib::MainLoop::new(None, false);
+ let pipeline = gst::Pipeline::new(None);
+
+ let (sender, receiver) = mpsc::channel();
+
+ for i in 0..SRC_NB {
+ let src =
+ gst::ElementFactory::make("ts-udpsrc", Some(format!("src-{}", i).as_str())).unwrap();
+ src.set_property("context", &format!("context-{}", (i as u32) % CONTEXT_NB))
+ .unwrap();
+ src.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+ src.set_property("port", &((FIRST_PORT + i) as u32))
+ .unwrap();
+
+ let queue =
+ gst::ElementFactory::make("ts-queue", Some(format!("queue-{}", i).as_str())).unwrap();
+ queue
+ .set_property("context", &format!("context-{}", (i as u32) % CONTEXT_NB))
+ .unwrap();
+ queue.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+
+ let sink =
+ gst::ElementFactory::make("appsink", Some(format!("sink-{}", i).as_str())).unwrap();
+ sink.set_property("sync", &false).unwrap();
+ sink.set_property("async", &false).unwrap();
+ sink.set_property("emit-signals", &true).unwrap();
+
+ pipeline.add_many(&[&src, &queue, &sink]).unwrap();
+ gst::Element::link_many(&[&src, &queue, &sink]).unwrap();
+
+ let appsink = sink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let sender_clone = sender.clone();
+ appsink.connect_new_sample(move |appsink| {
+ let _sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ sender_clone.send(()).unwrap();
+ Ok(gst::FlowSuccess::Ok)
+ });
+ }
+
+ let pipeline_clone = pipeline.clone();
+ let l_clone = l.clone();
+ let mut test_scenario = Some(move || {
+ let buffer = [0; 160];
+ let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
+
+ let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let destinations = (FIRST_PORT..(FIRST_PORT + SRC_NB))
+ .map(|port| SocketAddr::new(ipaddr, port))
+ .collect::<Vec<_>>();
+
+ for _ in 0..BUFFER_NB {
+ for dest in &destinations {
+ gst_debug!(CAT, "multiple_contexts_queue: sending buffer to {:?}", dest);
+ socket.send_to(&buffer, dest).unwrap();
+ std::thread::sleep(std::time::Duration::from_millis(CONTEXT_WAIT as u64));
+ }
+ }
+
+ gst_debug!(
+ CAT,
+ "multiple_contexts_queue: waiting for all buffers notifications"
+ );
+ for _ in 0..(BUFFER_NB * (SRC_NB as u32)) {
+ receiver.recv().unwrap();
+ }
+
+ pipeline_clone.set_state(gst::State::Null).unwrap();
+ l_clone.quit();
+ });
+
+ let bus = pipeline.get_bus().unwrap();
+ let l_clone = l.clone();
+ bus.add_watch(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::StateChanged(state_changed) => {
+ if let Some(source) = state_changed.get_src() {
+ if source.get_type() == gst::Pipeline::static_type()
+ && state_changed.get_old() == gst::State::Paused
+ && state_changed.get_current() == gst::State::Playing
+ {
+ if let Some(test_scenario) = test_scenario.take() {
+ std::thread::spawn(test_scenario);
+ }
+ }
+ }
+ }
+ MessageView::Error(err) => {
+ gst_error!(
+ CAT,
+ "multiple_contexts_queue: Error from {:?}: {} ({:?})",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error(),
+ err.get_debug()
+ );
+ l_clone.quit();
+ }
+ _ => (),
+ };
+
+ glib::Continue(true)
+ })
+ .unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "Starting main loop for multiple_contexts_queue...");
+ l.run();
+ gst_debug!(CAT, "Stopping main loop for multiple_contexts_queue...");
+}
+
+#[test]
+fn multiple_contexts_proxy() {
+ use std::net;
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+
+ init();
+
+ const CONTEXT_NB: u32 = 2;
+ const SRC_NB: u16 = 4;
+ const CONTEXT_WAIT: u32 = 1;
+ const BUFFER_NB: u32 = 3;
+ // Don't overlap with `multiple_contexts_queue`
+ const OFFSET: u16 = 10;
+ const FIRST_PORT: u16 = 40000 + OFFSET;
+
+ let l = glib::MainLoop::new(None, false);
+ let pipeline = gst::Pipeline::new(None);
+
+ let (sender, receiver) = mpsc::channel();
+
+ for i in 0..SRC_NB {
+ let pipeline_index = i + OFFSET;
+
+ let src = gst::ElementFactory::make(
+ "ts-udpsrc",
+ Some(format!("src-{}", pipeline_index).as_str()),
+ )
+ .unwrap();
+ src.set_property("context", &format!("context-{}", (i as u32) % CONTEXT_NB))
+ .unwrap();
+ src.set_property("context-wait", &CONTEXT_WAIT).unwrap();
+ src.set_property("port", &((FIRST_PORT + i) as u32))
+ .unwrap();
+
+ let proxysink = gst::ElementFactory::make(
+ "ts-proxysink",
+ Some(format!("proxysink-{}", pipeline_index).as_str()),
+ )
+ .unwrap();
+ proxysink
+ .set_property("proxy-context", &format!("proxy-{}", pipeline_index))
+ .unwrap();
+ let proxysrc = gst::ElementFactory::make(
+ "ts-proxysrc",
+ Some(format!("proxysrc-{}", pipeline_index).as_str()),
+ )
+ .unwrap();
+ proxysrc
+ .set_property(
+ "context",
+ &format!("context-{}", (pipeline_index as u32) % CONTEXT_NB),
+ )
+ .unwrap();
+ proxysrc
+ .set_property("proxy-context", &format!("proxy-{}", pipeline_index))
+ .unwrap();
+
+ let sink =
+ gst::ElementFactory::make("appsink", Some(format!("sink-{}", pipeline_index).as_str()))
+ .unwrap();
+ sink.set_property("sync", &false).unwrap();
+ sink.set_property("async", &false).unwrap();
+ sink.set_property("emit-signals", &true).unwrap();
+
+ pipeline
+ .add_many(&[&src, &proxysink, &proxysrc, &sink])
+ .unwrap();
+ src.link(&proxysink).unwrap();
+ proxysrc.link(&sink).unwrap();
+
+ let appsink = sink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let sender_clone = sender.clone();
+ appsink.connect_new_sample(move |appsink| {
+ let _sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ sender_clone.send(()).unwrap();
+ Ok(gst::FlowSuccess::Ok)
+ });
+ }
+
+ let pipeline_clone = pipeline.clone();
+ let l_clone = l.clone();
+ let mut test_scenario = Some(move || {
+ let buffer = [0; 160];
+ let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
+
+ let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let destinations = (FIRST_PORT..(FIRST_PORT + SRC_NB))
+ .map(|port| SocketAddr::new(ipaddr, port))
+ .collect::<Vec<_>>();
+
+ for _ in 0..BUFFER_NB {
+ for dest in &destinations {
+ gst_debug!(CAT, "multiple_contexts_proxy: sending buffer to {:?}", dest);
+ socket.send_to(&buffer, dest).unwrap();
+ std::thread::sleep(std::time::Duration::from_millis(CONTEXT_WAIT as u64));
+ }
+ }
+
+ gst_debug!(
+ CAT,
+ "multiple_contexts_proxy: waiting for all buffers notifications"
+ );
+ for _ in 0..(BUFFER_NB * (SRC_NB as u32)) {
+ receiver.recv().unwrap();
+ }
+
+ pipeline_clone.set_state(gst::State::Null).unwrap();
+ l_clone.quit();
+ });
+
+ let bus = pipeline.get_bus().unwrap();
+ let l_clone = l.clone();
+ bus.add_watch(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::StateChanged(state_changed) => {
+ if let Some(source) = state_changed.get_src() {
+ if source.get_type() == gst::Pipeline::static_type()
+ && state_changed.get_old() == gst::State::Paused
+ && state_changed.get_current() == gst::State::Playing
+ {
+ if let Some(test_scenario) = test_scenario.take() {
+ std::thread::spawn(test_scenario);
+ }
+ }
+ }
+ }
+ MessageView::Error(err) => {
+ gst_error!(
+ CAT,
+ "multiple_contexts_proxy: Error from {:?}: {} ({:?})",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error(),
+ err.get_debug()
+ );
+ l_clone.quit();
+ }
+ _ => (),
+ };
+
+ glib::Continue(true)
+ })
+ .unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "Starting main loop for multiple_contexts_proxy...");
+ l.run();
+ gst_debug!(CAT, "Stopping main loop for multiple_contexts_proxy...");
+}
+
+#[test]
+fn eos() {
+ const CONTEXT: &str = "test_eos";
+
+ init();
+
+ let l = glib::MainLoop::new(None, false);
+ let pipeline = gst::Pipeline::new(None);
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+
+ let src = gst::ElementFactory::make("ts-appsrc", Some("src-eos")).unwrap();
+ src.set_property("caps", &caps).unwrap();
+ src.set_property("do-timestamp", &true).unwrap();
+ src.set_property("context", &CONTEXT).unwrap();
+
+ let queue = gst::ElementFactory::make("ts-queue", Some("queue-eos")).unwrap();
+ queue.set_property("context", &CONTEXT).unwrap();
+
+ let appsink = gst::ElementFactory::make("appsink", Some("sink-eos")).unwrap();
+
+ pipeline.add_many(&[&src, &queue, &appsink]).unwrap();
+ gst::Element::link_many(&[&src, &queue, &appsink]).unwrap();
+
+ appsink.set_property("sync", &false).unwrap();
+ appsink.set_property("async", &false).unwrap();
+
+ appsink.set_property("emit-signals", &true).unwrap();
+ let (sample_notifier, sample_notif_rcv) = mpsc::channel();
+ let (eos_notifier, eos_notif_rcv) = mpsc::channel();
+ let appsink = appsink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ appsink.connect_new_sample(move |appsink| {
+ gst_debug!(CAT, obj: appsink, "eos: pulling sample");
+ let _ = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ sample_notifier.send(()).unwrap();
+
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ appsink.connect_eos(move |_appsink| eos_notifier.send(()).unwrap());
+
+ fn push_buffer(src: &gst::Element) -> bool {
+ gst_debug!(CAT, obj: src, "eos: pushing buffer");
+ src.emit("push-buffer", &[&gst::Buffer::from_slice(vec![0; 1024])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap()
+ }
+
+ let pipeline_clone = pipeline.clone();
+ let l_clone = l.clone();
+ let mut scenario = Some(move || {
+ // Initialize the dataflow
+ assert!(push_buffer(&src));
+
+ sample_notif_rcv.recv().unwrap();
+
+ assert!(src
+ .emit("end-of-stream", &[])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap());
+
+ eos_notif_rcv.recv().unwrap();
+
+ assert!(push_buffer(&src));
+ std::thread::sleep(std::time::Duration::from_millis(50));
+ assert_eq!(
+ sample_notif_rcv.try_recv().unwrap_err(),
+ mpsc::TryRecvError::Empty
+ );
+
+ pipeline_clone.set_state(gst::State::Null).unwrap();
+ l_clone.quit();
+ });
+
+ let l_clone = l.clone();
+ pipeline
+ .get_bus()
+ .unwrap()
+ .add_watch(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::StateChanged(state_changed) => {
+ if let Some(source) = state_changed.get_src() {
+ if source.get_type() != gst::Pipeline::static_type() {
+ return glib::Continue(true);
+ }
+ if state_changed.get_old() == gst::State::Paused
+ && state_changed.get_current() == gst::State::Playing
+ {
+ if let Some(scenario) = scenario.take() {
+ std::thread::spawn(scenario);
+ }
+ }
+ }
+ }
+ MessageView::Error(err) => {
+ gst_error!(
+ CAT,
+ "eos: Error from {:?}: {} ({:?})",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error(),
+ err.get_debug()
+ );
+ l_clone.quit();
+ }
+ _ => (),
+ };
+
+ glib::Continue(true)
+ })
+ .unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "Starting main loop for eos...");
+ l.run();
+ gst_debug!(CAT, "Stopping main loop for eos...");
+}
+
+#[test]
+fn premature_shutdown() {
+ init();
+
+ const APPSRC_CONTEXT_WAIT: u32 = 0;
+ const QUEUE_CONTEXT_WAIT: u32 = 1;
+ const QUEUE_ITEMS_CAPACITY: u32 = 1;
+
+ let l = glib::MainLoop::new(None, false);
+ let pipeline = gst::Pipeline::new(None);
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+
+ let src = gst::ElementFactory::make("ts-appsrc", Some("src-ps")).unwrap();
+ src.set_property("caps", &caps).unwrap();
+ src.set_property("do-timestamp", &true).unwrap();
+ src.set_property("context", &"appsrc-context").unwrap();
+ src.set_property("context-wait", &APPSRC_CONTEXT_WAIT)
+ .unwrap();
+
+ let queue = gst::ElementFactory::make("ts-queue", Some("queue-ps")).unwrap();
+ queue.set_property("context", &"queue-context").unwrap();
+ queue
+ .set_property("context-wait", &QUEUE_CONTEXT_WAIT)
+ .unwrap();
+ queue
+ .set_property("max-size-buffers", &QUEUE_ITEMS_CAPACITY)
+ .unwrap();
+
+ let appsink = gst::ElementFactory::make("appsink", Some("sink-ps")).unwrap();
+
+ pipeline.add_many(&[&src, &queue, &appsink]).unwrap();
+ gst::Element::link_many(&[&src, &queue, &appsink]).unwrap();
+
+ appsink.set_property("emit-signals", &true).unwrap();
+ appsink.set_property("sync", &false).unwrap();
+ appsink.set_property("async", &false).unwrap();
+
+ let (appsink_sender, appsink_receiver) = mpsc::channel();
+
+ let appsink = appsink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ appsink.connect_new_sample(move |appsink| {
+ gst_debug!(CAT, obj: appsink, "premature_shutdown: pulling sample");
+ let _sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ appsink_sender.send(()).unwrap();
+
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ fn push_buffer(src: &gst::Element, intent: &str) -> bool {
+ gst_debug!(
+ CAT,
+ obj: src,
+ "premature_shutdown: pushing buffer {}",
+ intent
+ );
+ src.emit("push-buffer", &[&gst::Buffer::from_slice(vec![0; 1024])])
+ .unwrap()
+ .unwrap()
+ .get_some::<bool>()
+ .unwrap()
+ }
+
+ let pipeline_clone = pipeline.clone();
+ let l_clone = l.clone();
+ let mut scenario = Some(move || {
+ gst_debug!(CAT, "premature_shutdown: STEP 1: Playing");
+ // Initialize the dataflow
+ assert!(push_buffer(&src, "(initial)"));
+
+ // Wait for the buffer to reach AppSink
+ appsink_receiver.recv().unwrap();
+ assert_eq!(
+ appsink_receiver.try_recv().unwrap_err(),
+ mpsc::TryRecvError::Empty
+ );
+
+ assert!(push_buffer(&src, "before Playing -> Paused"));
+
+ gst_debug!(CAT, "premature_shutdown: STEP 2: Playing -> Paused");
+ pipeline_clone.set_state(gst::State::Paused).unwrap();
+
+ gst_debug!(CAT, "premature_shutdown: STEP 3: Paused -> Playing");
+ pipeline_clone.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "premature_shutdown: Playing again");
+
+ gst_debug!(CAT, "Waiting for buffer sent before Playing -> Paused");
+ appsink_receiver.recv().unwrap();
+
+ assert!(push_buffer(&src, "after Paused -> Playing"));
+ gst_debug!(CAT, "Waiting for buffer sent after Paused -> Playing");
+ appsink_receiver.recv().unwrap();
+
+ // Fill up the (dataqueue) and abruptly shutdown
+ assert!(push_buffer(&src, "filling 1"));
+ assert!(push_buffer(&src, "filling 2"));
+
+ gst_debug!(CAT, "premature_shutdown: STEP 4: Playing -> Null");
+
+ pipeline_clone.set_state(gst::State::Null).unwrap();
+
+ assert!(!push_buffer(&src, "after Null"));
+
+ l_clone.quit();
+ });
+
+ let l_clone = l.clone();
+ pipeline
+ .get_bus()
+ .unwrap()
+ .add_watch(move |_, msg| {
+ use gst::MessageView;
+
+ match msg.view() {
+ MessageView::StateChanged(state_changed) => {
+ if let Some(source) = state_changed.get_src() {
+ if source.get_type() != gst::Pipeline::static_type() {
+ return glib::Continue(true);
+ }
+ if state_changed.get_old() == gst::State::Paused
+ && state_changed.get_current() == gst::State::Playing
+ {
+ if let Some(scenario) = scenario.take() {
+ std::thread::spawn(scenario);
+ }
+ }
+ }
+ }
+ MessageView::Error(err) => {
+ gst_error!(
+ CAT,
+ "premature_shutdown: Error from {:?}: {} ({:?})",
+ err.get_src().map(|s| s.get_path_string()),
+ err.get_error(),
+ err.get_debug()
+ );
+ l_clone.quit();
+ }
+ _ => (),
+ };
+
+ glib::Continue(true)
+ })
+ .unwrap();
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ gst_debug!(CAT, "Starting main loop for premature_shutdown...");
+ l.run();
+ gst_debug!(CAT, "Stopped main loop for premature_shutdown...");
+}
diff --git a/generic/gst-plugin-threadshare/tests/proxy.rs b/generic/gst-plugin-threadshare/tests/proxy.rs
new file mode 100644
index 000000000..b593b16eb
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/proxy.rs
@@ -0,0 +1,167 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+
+use gst_app;
+
+use std::sync::{Arc, Mutex};
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare proxy test");
+ });
+}
+
+#[test]
+fn test_push() {
+ init();
+
+ let pipeline = gst::Pipeline::new(None);
+ let fakesrc = gst::ElementFactory::make("fakesrc", None).unwrap();
+ let proxysink = gst::ElementFactory::make("ts-proxysink", None).unwrap();
+ let proxysrc = gst::ElementFactory::make("ts-proxysrc", None).unwrap();
+ let appsink = gst::ElementFactory::make("appsink", None).unwrap();
+
+ pipeline
+ .add_many(&[&fakesrc, &proxysink, &proxysrc, &appsink])
+ .unwrap();
+ fakesrc.link(&proxysink).unwrap();
+ proxysrc.link(&appsink).unwrap();
+
+ fakesrc.set_property("num-buffers", &3i32).unwrap();
+ proxysink.set_property("proxy-context", &"test1").unwrap();
+ proxysrc.set_property("proxy-context", &"test1").unwrap();
+
+ appsink.set_property("emit-signals", &true).unwrap();
+
+ let samples = Arc::new(Mutex::new(Vec::new()));
+
+ let appsink = appsink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let samples_clone = samples.clone();
+ appsink.connect_new_sample(move |appsink| {
+ let sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ samples_clone.lock().unwrap().push(sample);
+
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ let mut eos = false;
+ let bus = pipeline.get_bus().unwrap();
+ while let Some(msg) = bus.timed_pop(5 * gst::SECOND) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Eos(..) => {
+ eos = true;
+ break;
+ }
+ MessageView::Error(err) => unreachable!("proxy::test_push {:?}", err),
+ _ => (),
+ }
+ }
+
+ assert!(eos);
+ let samples = samples.lock().unwrap();
+ assert_eq!(samples.len(), 3);
+
+ for sample in samples.iter() {
+ assert!(sample.get_buffer().is_some());
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn test_from_pipeline_to_pipeline() {
+ init();
+
+ let pipe_1 = gst::Pipeline::new(None);
+ let fakesrc = gst::ElementFactory::make("fakesrc", None).unwrap();
+ let pxsink = gst::ElementFactory::make("ts-proxysink", None).unwrap();
+
+ let pipe_2 = gst::Pipeline::new(None);
+ let pxsrc = gst::ElementFactory::make("ts-proxysrc", None).unwrap();
+ let fakesink = gst::ElementFactory::make("fakesink", None).unwrap();
+
+ pipe_1.add_many(&[&fakesrc, &pxsink]).unwrap();
+ fakesrc.link(&pxsink).unwrap();
+
+ pipe_2.add_many(&[&pxsrc, &fakesink]).unwrap();
+ pxsrc.link(&fakesink).unwrap();
+
+ pxsink.set_property("proxy-context", &"test2").unwrap();
+ pxsrc.set_property("proxy-context", &"test2").unwrap();
+
+ pipe_1.set_state(gst::State::Paused).unwrap();
+ pipe_2.set_state(gst::State::Paused).unwrap();
+
+ let _ = pipe_1.get_state(gst::CLOCK_TIME_NONE);
+ let _ = pipe_2.get_state(gst::CLOCK_TIME_NONE);
+
+ pipe_1.set_state(gst::State::Null).unwrap();
+
+ pipe_2.set_state(gst::State::Null).unwrap();
+}
+
+#[test]
+fn test_from_pipeline_to_pipeline_and_back() {
+ init();
+
+ let pipe_1 = gst::Pipeline::new(None);
+ let pxsrc_1 = gst::ElementFactory::make("ts-proxysrc", None).unwrap();
+ let pxsink_1 = gst::ElementFactory::make("ts-proxysink", None).unwrap();
+
+ let pipe_2 = gst::Pipeline::new(None);
+ let pxsrc_2 = gst::ElementFactory::make("ts-proxysrc", None).unwrap();
+ let pxsink_2 = gst::ElementFactory::make("ts-proxysink", None).unwrap();
+
+ pipe_1.add_many(&[&pxsrc_1, &pxsink_1]).unwrap();
+ pxsrc_1.link(&pxsink_1).unwrap();
+
+ pipe_2.add_many(&[&pxsrc_2, &pxsink_2]).unwrap();
+ pxsrc_2.link(&pxsink_2).unwrap();
+
+ pxsrc_1.set_property("proxy-context", &"test3").unwrap();
+ pxsink_2.set_property("proxy-context", &"test3").unwrap();
+
+ pxsrc_2.set_property("proxy-context", &"test4").unwrap();
+ pxsink_1.set_property("proxy-context", &"test4").unwrap();
+
+ pipe_1.set_state(gst::State::Paused).unwrap();
+ pipe_2.set_state(gst::State::Paused).unwrap();
+
+ pipe_1.set_state(gst::State::Null).unwrap();
+ pipe_2.set_state(gst::State::Null).unwrap();
+}
diff --git a/generic/gst-plugin-threadshare/tests/queue.rs b/generic/gst-plugin-threadshare/tests/queue.rs
new file mode 100644
index 000000000..c170516fe
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/queue.rs
@@ -0,0 +1,100 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib;
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+
+use gst_app;
+
+use std::sync::{Arc, Mutex};
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare queue test");
+ });
+}
+
+#[test]
+fn test_push() {
+ init();
+
+ let pipeline = gst::Pipeline::new(None);
+ let fakesrc = gst::ElementFactory::make("fakesrc", None).unwrap();
+ let queue = gst::ElementFactory::make("ts-queue", None).unwrap();
+ let appsink = gst::ElementFactory::make("appsink", None).unwrap();
+
+ pipeline.add_many(&[&fakesrc, &queue, &appsink]).unwrap();
+ fakesrc.link(&queue).unwrap();
+ queue.link(&appsink).unwrap();
+
+ fakesrc.set_property("num-buffers", &3i32).unwrap();
+
+ appsink.set_property("emit-signals", &true).unwrap();
+
+ let samples = Arc::new(Mutex::new(Vec::new()));
+
+ let appsink = appsink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let samples_clone = samples.clone();
+ appsink.connect_new_sample(move |appsink| {
+ let sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ samples_clone.lock().unwrap().push(sample);
+
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ let mut eos = false;
+ let bus = pipeline.get_bus().unwrap();
+ while let Some(msg) = bus.timed_pop(5 * gst::SECOND) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Eos(..) => {
+ eos = true;
+ break;
+ }
+ MessageView::Error(..) => unreachable!(),
+ _ => (),
+ }
+ }
+
+ assert!(eos);
+ let samples = samples.lock().unwrap();
+ assert_eq!(samples.len(), 3);
+
+ for sample in samples.iter() {
+ assert!(sample.get_buffer().is_some());
+ }
+
+ pipeline.set_state(gst::State::Null).unwrap();
+}
diff --git a/generic/gst-plugin-threadshare/tests/tcpclientsrc.rs b/generic/gst-plugin-threadshare/tests/tcpclientsrc.rs
new file mode 100644
index 000000000..7498dcc7e
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/tcpclientsrc.rs
@@ -0,0 +1,124 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+// Copyright (C) 2018 LEE Dongjun <redongjun@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib;
+use glib::prelude::*;
+
+use gst;
+use gst::prelude::*;
+
+use gst_app;
+
+use std::io::Write;
+use std::sync::{Arc, Mutex};
+use std::{thread, time};
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare tcpclientsrc test");
+ });
+}
+
+#[test]
+fn test_push() {
+ init();
+
+ let handler = thread::spawn(move || {
+ use std::net;
+
+ let listener = net::TcpListener::bind("0.0.0.0:5000").unwrap();
+ let stream = listener.incoming().next().unwrap();
+ let buffer = [0; 160];
+ let mut socket = stream.unwrap();
+ for _ in 0..3 {
+ let _ = socket.write(&buffer);
+ thread::sleep(time::Duration::from_millis(20));
+ }
+ });
+
+ let pipeline = gst::Pipeline::new(None);
+
+ let tcpclientsrc = gst::ElementFactory::make("ts-tcpclientsrc", None).unwrap();
+ let appsink = gst::ElementFactory::make("appsink", None).unwrap();
+ appsink.set_property("sync", &false).unwrap();
+ appsink.set_property("async", &false).unwrap();
+
+ pipeline.add_many(&[&tcpclientsrc, &appsink]).unwrap();
+ tcpclientsrc.link(&appsink).unwrap();
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ tcpclientsrc.set_property("caps", &caps).unwrap();
+ tcpclientsrc.set_property("port", &(5000u32)).unwrap();
+
+ appsink.set_property("emit-signals", &true).unwrap();
+
+ let samples = Arc::new(Mutex::new(Vec::new()));
+
+ let appsink = appsink.dynamic_cast::<gst_app::AppSink>().unwrap();
+ let samples_clone = samples.clone();
+ appsink.connect_new_sample(move |appsink| {
+ let sample = appsink
+ .emit("pull-sample", &[])
+ .unwrap()
+ .unwrap()
+ .get::<gst::Sample>()
+ .unwrap()
+ .unwrap();
+
+ let mut samples = samples_clone.lock().unwrap();
+ samples.push(sample);
+ Ok(gst::FlowSuccess::Ok)
+ });
+
+ pipeline.set_state(gst::State::Playing).unwrap();
+
+ let mut eos = false;
+ let bus = pipeline.get_bus().unwrap();
+ while let Some(msg) = bus.timed_pop(5 * gst::SECOND) {
+ use gst::MessageView;
+ match msg.view() {
+ MessageView::Eos(..) => {
+ eos = true;
+ break;
+ }
+ MessageView::Error(..) => unreachable!(),
+ _ => (),
+ }
+ }
+
+ assert!(eos);
+ let samples = samples.lock().unwrap();
+ for sample in samples.iter() {
+ assert_eq!(Some(caps.as_ref()), sample.get_caps());
+ }
+
+ let total_received_size = samples.iter().fold(0, |acc, sample| {
+ acc + sample.get_buffer().unwrap().get_size()
+ });
+ assert_eq!(total_received_size, 3 * 160);
+
+ pipeline.set_state(gst::State::Null).unwrap();
+
+ handler.join().unwrap();
+}
diff --git a/generic/gst-plugin-threadshare/tests/udpsink.rs b/generic/gst-plugin-threadshare/tests/udpsink.rs
new file mode 100644
index 000000000..420b34fe2
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/udpsink.rs
@@ -0,0 +1,160 @@
+// Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use std::thread;
+
+use glib;
+use glib::prelude::*;
+
+use gst;
+use gst_check;
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare udpsrc test");
+ });
+}
+
+#[test]
+fn test_client_management() {
+ init();
+
+ let h = gst_check::Harness::new("ts-udpsink");
+ let udpsink = h.get_element().unwrap();
+
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(clients, "127.0.0.1:5000");
+
+ udpsink.emit("add", &[&"192.168.1.1", &57]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000,192.168.1.1:57");
+
+ /* Adding a client twice is not supported */
+ udpsink.emit("add", &[&"192.168.1.1", &57]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000,192.168.1.1:57");
+
+ udpsink.emit("remove", &[&"192.168.1.1", &57]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000");
+
+ /* Removing a non-existing client should not be a problem */
+ udpsink.emit("remove", &[&"192.168.1.1", &57]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000");
+
+ /* While the default host:address client is listed in clients,
+ * it can't be removed with the remove signal */
+ udpsink.emit("remove", &[&"127.0.0.1", &5000]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000");
+
+ /* It is however possible to remove the default client by setting
+ * host to None */
+ let host: Option<String> = None;
+ udpsink.set_property("host", &host).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "");
+
+ /* The client properties is writable too */
+ udpsink
+ .set_property("clients", &"127.0.0.1:5000,192.168.1.1:57")
+ .unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "127.0.0.1:5000,192.168.1.1:57");
+
+ udpsink.emit("clear", &[]).unwrap();
+ let clients = udpsink
+ .get_property("clients")
+ .unwrap()
+ .get::<String>()
+ .unwrap()
+ .unwrap();
+ assert_eq!(clients, "");
+}
+
+#[test]
+fn test_chain() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-udpsink");
+ h.set_src_caps_str(&"foo/bar");
+
+ thread::spawn(move || {
+ use std::net;
+ use std::time;
+
+ thread::sleep(time::Duration::from_millis(50));
+
+ let socket = net::UdpSocket::bind("127.0.0.1:5000").unwrap();
+ let mut buf = [0; 5];
+ let (amt, _) = socket.recv_from(&mut buf).unwrap();
+
+ assert!(amt == 4);
+ assert!(buf == [42, 43, 44, 45, 0]);
+ });
+
+ let buf = gst::Buffer::from_slice(&[42, 43, 44, 45]);
+ assert!(h.push(buf) == Ok(gst::FlowSuccess::Ok));
+}
diff --git a/generic/gst-plugin-threadshare/tests/udpsrc.rs b/generic/gst-plugin-threadshare/tests/udpsrc.rs
new file mode 100644
index 000000000..eadfac743
--- /dev/null
+++ b/generic/gst-plugin-threadshare/tests/udpsrc.rs
@@ -0,0 +1,174 @@
+// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the
+// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+// Boston, MA 02110-1335, USA.
+
+use glib;
+use glib::prelude::*;
+
+use gio;
+
+use gst;
+use gst_check;
+
+use std::thread;
+
+use gstthreadshare;
+
+fn init() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ gst::init().unwrap();
+ gstthreadshare::plugin_register_static().expect("gstthreadshare udpsrc test");
+ });
+}
+
+#[test]
+fn test_push() {
+ init();
+
+ let mut h = gst_check::Harness::new("ts-udpsrc");
+
+ let caps = gst::Caps::new_simple("foo/bar", &[]);
+ {
+ let udpsrc = h.get_element().unwrap();
+ udpsrc.set_property("caps", &caps).unwrap();
+ udpsrc.set_property("port", &(5000 as u32)).unwrap();
+ udpsrc.set_property("context", &"test-push").unwrap();
+ }
+
+ h.play();
+
+ thread::spawn(move || {
+ use std::net;
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ use std::time;
+
+ // Sleep 50ms to allow for the udpsrc to be ready to actually receive data
+ thread::sleep(time::Duration::from_millis(50));
+
+ let buffer = [0; 160];
+ let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
+
+ let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let dest = SocketAddr::new(ipaddr, 5000 as u16);
+
+ for _ in 0..3 {
+ socket.send_to(&buffer, dest).unwrap();
+ }
+ });
+
+ for _ in 0..3 {
+ let buffer = h.pull().unwrap();
+ assert_eq!(buffer.get_size(), 160);
+ }
+
+ let mut n_events = 0;
+ loop {
+ use gst::EventView;
+
+ let event = h.pull_event().unwrap();
+ match event.view() {
+ EventView::StreamStart(..) => {
+ assert_eq!(n_events, 0);
+ }
+ EventView::Caps(ev) => {
+ assert_eq!(n_events, 1);
+ let event_caps = ev.get_caps();
+ assert_eq!(caps.as_ref(), event_caps);
+ }
+ EventView::Segment(..) => {
+ assert_eq!(n_events, 2);
+ break;
+ }
+ _ => (),
+ }
+ n_events += 1;
+ }
+ assert!(n_events >= 2);
+}
+
+#[test]
+#[cfg(not(windows))]
+fn test_socket_reuse() {
+ init();
+
+ let mut ts_src_h = gst_check::Harness::new("ts-udpsrc");
+ let mut sink_h = gst_check::Harness::new("udpsink");
+ let mut ts_src_h2 = gst_check::Harness::new("ts-udpsrc");
+
+ {
+ let udpsrc = ts_src_h.get_element().unwrap();
+ udpsrc.set_property("port", &(6000 as u32)).unwrap();
+ udpsrc
+ .set_property("context", &"test-socket-reuse")
+ .unwrap();
+ }
+ ts_src_h.play();
+
+ {
+ let udpsrc = ts_src_h.get_element().unwrap();
+ let socket = udpsrc
+ .get_property("used-socket")
+ .unwrap()
+ .get::<gio::Socket>()
+ .unwrap();
+
+ let udpsink = sink_h.get_element().unwrap();
+ udpsink.set_property("socket", &socket).unwrap();
+ udpsink.set_property("host", &"127.0.0.1").unwrap();
+ udpsink.set_property("port", &6001i32).unwrap();
+ }
+ sink_h.play();
+ sink_h.set_src_caps_str("application/test");
+
+ {
+ let udpsrc = ts_src_h2.get_element().unwrap();
+ udpsrc.set_property("port", &(6001 as u32)).unwrap();
+ udpsrc
+ .set_property("context", &"test-socket-reuse")
+ .unwrap();
+ }
+ ts_src_h2.play();
+
+ thread::spawn(move || {
+ use std::net;
+ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ use std::time;
+
+ // Sleep 50ms to allow for the udpsrc to be ready to actually receive data
+ thread::sleep(time::Duration::from_millis(50));
+
+ let buffer = [0; 160];
+ let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
+
+ let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let dest = SocketAddr::new(ipaddr, 6000 as u16);
+
+ for _ in 0..3 {
+ socket.send_to(&buffer, dest).unwrap();
+ }
+ });
+
+ for _ in 0..3 {
+ let buffer = ts_src_h.pull().unwrap();
+ sink_h.push(buffer).unwrap();
+ let buffer = ts_src_h2.pull().unwrap();
+
+ assert_eq!(buffer.get_size(), 160);
+ }
+}