From af0337c26c25cc782142f914dfa19383e51e1743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 14 Nov 2020 19:09:42 +0200 Subject: generic: Update for subclassing API changes --- generic/file/src/filesink.rs | 337 ---- generic/file/src/filesink/imp.rs | 320 ++++ generic/file/src/filesink/mod.rs | 29 + generic/file/src/filesrc.rs | 391 ----- generic/file/src/filesrc/imp.rs | 373 +++++ generic/file/src/filesrc/mod.rs | 28 + generic/sodium/src/decrypter.rs | 727 -------- generic/sodium/src/decrypter/imp.rs | 723 ++++++++ generic/sodium/src/decrypter/mod.rs | 43 + generic/sodium/src/encrypter.rs | 568 ------- generic/sodium/src/encrypter/imp.rs | 564 +++++++ generic/sodium/src/encrypter/mod.rs | 43 + generic/threadshare/src/appsrc.rs | 746 --------- generic/threadshare/src/appsrc/imp.rs | 737 ++++++++ generic/threadshare/src/appsrc/mod.rs | 40 + generic/threadshare/src/inputselector.rs | 640 ------- generic/threadshare/src/inputselector/imp.rs | 630 +++++++ generic/threadshare/src/inputselector/mod.rs | 39 + generic/threadshare/src/jitterbuffer/ffi.rs | 115 ++ generic/threadshare/src/jitterbuffer/imp.rs | 1636 ++++++++++++++++++ .../threadshare/src/jitterbuffer/jitterbuffer.rs | 1764 +++----------------- generic/threadshare/src/jitterbuffer/mod.rs | 473 +----- generic/threadshare/src/lib.rs | 2 +- generic/threadshare/src/proxy.rs | 1334 --------------- generic/threadshare/src/proxy/imp.rs | 1319 +++++++++++++++ generic/threadshare/src/proxy/mod.rs | 54 + generic/threadshare/src/queue.rs | 897 ---------- generic/threadshare/src/queue/imp.rs | 893 ++++++++++ generic/threadshare/src/queue/mod.rs | 39 + generic/threadshare/src/runtime/pad.rs | 90 +- generic/threadshare/src/tcpclientsrc.rs | 765 --------- generic/threadshare/src/tcpclientsrc/imp.rs | 756 +++++++++ generic/threadshare/src/tcpclientsrc/mod.rs | 40 + generic/threadshare/src/udpsink.rs | 1461 ---------------- generic/threadshare/src/udpsink/imp.rs | 1445 ++++++++++++++++ generic/threadshare/src/udpsink/mod.rs | 39 + generic/threadshare/src/udpsrc.rs | 983 ----------- generic/threadshare/src/udpsrc/imp.rs | 978 +++++++++++ generic/threadshare/src/udpsrc/mod.rs | 39 + generic/threadshare/tests/pad.rs | 1103 ++++++------ 40 files changed, 11825 insertions(+), 11378 deletions(-) delete mode 100644 generic/file/src/filesink.rs create mode 100644 generic/file/src/filesink/imp.rs create mode 100644 generic/file/src/filesink/mod.rs delete mode 100644 generic/file/src/filesrc.rs create mode 100644 generic/file/src/filesrc/imp.rs create mode 100644 generic/file/src/filesrc/mod.rs delete mode 100644 generic/sodium/src/decrypter.rs create mode 100644 generic/sodium/src/decrypter/imp.rs create mode 100644 generic/sodium/src/decrypter/mod.rs delete mode 100644 generic/sodium/src/encrypter.rs create mode 100644 generic/sodium/src/encrypter/imp.rs create mode 100644 generic/sodium/src/encrypter/mod.rs delete mode 100644 generic/threadshare/src/appsrc.rs create mode 100644 generic/threadshare/src/appsrc/imp.rs create mode 100644 generic/threadshare/src/appsrc/mod.rs delete mode 100644 generic/threadshare/src/inputselector.rs create mode 100644 generic/threadshare/src/inputselector/imp.rs create mode 100644 generic/threadshare/src/inputselector/mod.rs create mode 100644 generic/threadshare/src/jitterbuffer/ffi.rs create mode 100644 generic/threadshare/src/jitterbuffer/imp.rs delete mode 100644 generic/threadshare/src/proxy.rs create mode 100644 generic/threadshare/src/proxy/imp.rs create mode 100644 generic/threadshare/src/proxy/mod.rs delete mode 100644 generic/threadshare/src/queue.rs create mode 100644 generic/threadshare/src/queue/imp.rs create mode 100644 generic/threadshare/src/queue/mod.rs delete mode 100644 generic/threadshare/src/tcpclientsrc.rs create mode 100644 generic/threadshare/src/tcpclientsrc/imp.rs create mode 100644 generic/threadshare/src/tcpclientsrc/mod.rs delete mode 100644 generic/threadshare/src/udpsink.rs create mode 100644 generic/threadshare/src/udpsink/imp.rs create mode 100644 generic/threadshare/src/udpsink/mod.rs delete mode 100644 generic/threadshare/src/udpsrc.rs create mode 100644 generic/threadshare/src/udpsrc/imp.rs create mode 100644 generic/threadshare/src/udpsrc/mod.rs (limited to 'generic') diff --git a/generic/file/src/filesink.rs b/generic/file/src/filesink.rs deleted file mode 100644 index 2bc0b7c3e..000000000 --- a/generic/file/src/filesink.rs +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright (C) 2016-2017 Sebastian Dröge -// 2016 Luis de Bethencourt -// 2018 François Laignel -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , 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 = None; - -#[derive(Debug)] -struct Settings { - location: Option, -} - -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, - state: Mutex, -} - -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, - ) -> 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn new() -> Self { - Self { - settings: Mutex::new(Default::default()), - state: Mutex::new(Default::default()), - } - } - - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "File Sink", - "Sink/File", - "Write stream to a file", - "François Laignel , Luis de Bethencourt ", - ); - - 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 { - 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::().unwrap(); - - let res = match value.get::() { - 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 { - 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 { - 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 { - 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::().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 { - 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/file/src/filesink/imp.rs b/generic/file/src/filesink/imp.rs new file mode 100644 index 000000000..ae01b0849 --- /dev/null +++ b/generic/file/src/filesink/imp.rs @@ -0,0 +1,320 @@ +// Copyright (C) 2016-2017 Sebastian Dröge +// 2016 Luis de Bethencourt +// 2018 François Laignel +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 crate::file_location::FileLocation; + +const DEFAULT_LOCATION: Option = None; + +#[derive(Debug)] +struct Settings { + location: Option, +} + +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, + state: Mutex, +} + +lazy_static! { + static ref CAT: gst::DebugCategory = gst::DebugCategory::new( + "rsfilesink", + gst::DebugColorFlags::empty(), + Some("File Sink"), + ); +} + +impl FileSink { + fn set_location( + &self, + element: &super::FileSink, + location: Option, + ) -> 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 Type = super::FileSink; + type ParentType = gst_base::BaseSink; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + } + } + + fn type_init(type_: &mut subclass::InitializingType) { + type_.add_interface::(); + } + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "File Sink", + "Sink/File", + "Write stream to a file", + "François Laignel , Luis de Bethencourt ", + ); + + 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 { + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + match *prop { + subclass::Property("location", ..) => { + let res = match value.get::() { + Ok(Some(location)) => FileLocation::try_from_path_str(location) + .and_then(|file_location| self.set_location(obj, Some(file_location))), + Ok(None) => self.set_location(obj, None), + Err(_) => unreachable!("type checked upstream"), + }; + + if let Err(err) = res { + gst_error!(CAT, obj: obj, "Failed to set property `location`: {}", err); + } + } + _ => unimplemented!(), + }; + } + + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + 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: &Self::Type) -> 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: &Self::Type) -> 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: &Self::Type, + buffer: &gst::Buffer, + ) -> Result { + 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: &Self::Type) -> Option { + 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: &Self::Type, uri: &str) -> Result<(), glib::Error> { + // 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 { + vec!["file".to_string()] + } +} diff --git a/generic/file/src/filesink/mod.rs b/generic/file/src/filesink/mod.rs new file mode 100644 index 000000000..8ad677156 --- /dev/null +++ b/generic/file/src/filesink/mod.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2016-2017 Sebastian Dröge +// 2016 Luis de Bethencourt +// 2018 François Laignel +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct FileSink(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object, @implements gst::URIHandler; +} + +unsafe impl Send for FileSink {} +unsafe impl Sync for FileSink {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rsfilesink", + gst::Rank::None, + FileSink::static_type(), + ) +} diff --git a/generic/file/src/filesrc.rs b/generic/file/src/filesrc.rs deleted file mode 100644 index eb8d1d632..000000000 --- a/generic/file/src/filesrc.rs +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright (C) 2016-2017 Sebastian Dröge -// 2018 François Laignel -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , 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 = None; - -#[derive(Debug)] -struct Settings { - location: Option, -} - -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, - state: Mutex, -} - -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, - ) -> 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn new() -> Self { - Self { - settings: Mutex::new(Default::default()), - state: Mutex::new(Default::default()), - } - } - - fn type_init(type_: &mut subclass::InitializingType) { - type_.add_interface::(); - } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "File Source", - "Source/File", - "Read stream from a file", - "François Laignel , Sebastian Dröge ", - ); - - 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 { - 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::().unwrap(); - - let res = match value.get::() { - 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 { - 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::().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 { - 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 { - 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 { - 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::().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 { - 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/file/src/filesrc/imp.rs b/generic/file/src/filesrc/imp.rs new file mode 100644 index 000000000..ca58949dc --- /dev/null +++ b/generic/file/src/filesrc/imp.rs @@ -0,0 +1,373 @@ +// Copyright (C) 2016-2017 Sebastian Dröge +// 2018 François Laignel +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , 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 crate::file_location::FileLocation; + +const DEFAULT_LOCATION: Option = None; + +#[derive(Debug)] +struct Settings { + location: Option, +} + +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, + state: Mutex, +} + +lazy_static! { + static ref CAT: gst::DebugCategory = gst::DebugCategory::new( + "rsfilesrc", + gst::DebugColorFlags::empty(), + Some("File Source"), + ); +} + +impl FileSrc { + fn set_location( + &self, + element: &super::FileSrc, + location: Option, + ) -> 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 Type = super::FileSrc; + type ParentType = gst_base::BaseSrc; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + } + } + + fn type_init(type_: &mut subclass::InitializingType) { + type_.add_interface::(); + } + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "File Source", + "Source/File", + "Read stream from a file", + "François Laignel , Sebastian Dröge ", + ); + + 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 { + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + match *prop { + subclass::Property("location", ..) => { + let res = match value.get::() { + Ok(Some(location)) => FileLocation::try_from_path_str(location) + .and_then(|file_location| self.set_location(obj, Some(file_location))), + Ok(None) => self.set_location(obj, None), + Err(_) => unreachable!("type checked upstream"), + }; + + if let Err(err) = res { + gst_error!(CAT, obj: obj, "Failed to set property `location`: {}", err); + } + } + _ => unimplemented!(), + }; + } + + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.set_format(gst::Format::Bytes); + } +} + +impl ElementImpl for FileSrc {} + +impl BaseSrcImpl for FileSrc { + fn is_seekable(&self, _src: &Self::Type) -> bool { + true + } + + fn get_size(&self, _src: &Self::Type) -> Option { + 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: &Self::Type) -> 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: &Self::Type) -> 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: &Self::Type, + offset: u64, + _length: u32, + buffer: &mut gst::BufferRef, + ) -> Result { + 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: &Self::Type) -> Option { + 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: &Self::Type, uri: &str) -> Result<(), glib::Error> { + // 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 { + vec!["file".to_string()] + } +} diff --git a/generic/file/src/filesrc/mod.rs b/generic/file/src/filesrc/mod.rs new file mode 100644 index 000000000..70ed45de7 --- /dev/null +++ b/generic/file/src/filesrc/mod.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2016-2017 Sebastian Dröge +// 2018 François Laignel +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct FileSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object, @implements gst::URIHandler; +} + +unsafe impl Send for FileSrc {} +unsafe impl Sync for FileSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "rsfilesrc", + gst::Rank::None, + FileSrc::static_type(), + ) +} diff --git a/generic/sodium/src/decrypter.rs b/generic/sodium/src/decrypter.rs deleted file mode 100644 index 2de508b50..000000000 --- a/generic/sodium/src/decrypter.rs +++ /dev/null @@ -1,727 +0,0 @@ -// decrypter.rs -// -// Copyright 2019 Jordan Petridis -// -// 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, - sender_key: Option, -} - -#[derive(Debug)] -struct State { - adapter: gst_base::UniqueAdapter, - initial_nonce: Option, - precomputed_key: box_::PrecomputedKey, - block_size: Option, -} - -impl State { - fn from_props(props: &Props) -> Result { - 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 { - 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 { - 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, - state: Mutex>, -} - -impl Decrypter { - 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::Scheduling::new(); - 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::Duration::new(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 { - 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 { - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - let templ = klass.get_pad_template("sink").unwrap(); - let sinkpad = gst::Pad::from_template(&templ, Some("sink")); - - let templ = klass.get_pad_template("src").unwrap(); - let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) - .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), - ) - }) - .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) - }, - ) - }) - .query_function(|pad, parent, query| { - Decrypter::catch_panic_pad_function( - parent, - || false, - |decrypter, element| decrypter.src_query(pad, element, query), - ) - }) - .build(); - - let props = Mutex::new(Props::default()); - let state = Mutex::new(None); - - Self { - srcpad, - sinkpad, - props, - state, - } - } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Decrypter", - "Generic", - "libsodium-based file decrypter", - "Jordan Petridis ", - ); - - 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 { - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let element = obj.downcast_ref::().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 { - 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_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/sodium/src/decrypter/imp.rs b/generic/sodium/src/decrypter/imp.rs new file mode 100644 index 000000000..f82b874f2 --- /dev/null +++ b/generic/sodium/src/decrypter/imp.rs @@ -0,0 +1,723 @@ +// decrypter.rs +// +// Copyright 2019 Jordan Petridis +// +// 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, + sender_key: Option, +} + +#[derive(Debug)] +struct State { + adapter: gst_base::UniqueAdapter, + initial_nonce: Option, + precomputed_key: box_::PrecomputedKey, + block_size: Option, +} + +impl State { + fn from_props(props: &Props) -> Result { + 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: &super::Decrypter, + pad: &gst::Pad, + buffer: &gst::Buffer, + chunk_index: u64, + ) -> Result { + 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 { + 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") +} + +pub struct Decrypter { + srcpad: gst::Pad, + sinkpad: gst::Pad, + props: Mutex, + state: Mutex>, +} + +impl Decrypter { + fn src_activatemode_function( + &self, + _pad: &gst::Pad, + element: &super::Decrypter, + 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: &super::Decrypter, + 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::Scheduling::new(); + 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::Duration::new(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 - crate::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: &super::Decrypter) -> 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: &super::Decrypter, + requested_size: u32, + block_size: u32, + chunk_index: u64, + ) -> Result { + let pull_offset = crate::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: &super::Decrypter, + offset: u64, + buffer: Option<&mut gst::BufferRef>, + requested_size: u32, + ) -> Result { + 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 Type = super::Decrypter; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn with_class(klass: &Self::Class) -> Self { + let templ = klass.get_pad_template("sink").unwrap(); + let sinkpad = gst::Pad::from_template(&templ, Some("sink")); + + let templ = klass.get_pad_template("src").unwrap(); + let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) + .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), + ) + }) + .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) + }, + ) + }) + .query_function(|pad, parent, query| { + Decrypter::catch_panic_pad_function( + parent, + || false, + |decrypter, element| decrypter.src_query(pad, element, query), + ) + }) + .build(); + + let props = Mutex::new(Props::default()); + let state = Mutex::new(None); + + Self { + srcpad, + sinkpad, + props, + state, + } + } + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Decrypter", + "Generic", + "libsodium-based file decrypter", + "Jordan Petridis ", + ); + + 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 { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(&self.sinkpad).unwrap(); + obj.add_pad(&self.srcpad).unwrap(); + } + + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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) + } +} diff --git a/generic/sodium/src/decrypter/mod.rs b/generic/sodium/src/decrypter/mod.rs new file mode 100644 index 000000000..4bee6635c --- /dev/null +++ b/generic/sodium/src/decrypter/mod.rs @@ -0,0 +1,43 @@ +// decrypter.rs +// +// Copyright 2019 Jordan Petridis +// +// 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::*; + +mod imp; + +glib_wrapper! { + pub struct Decrypter(ObjectSubclass) @extends gst::Element, gst::Object; +} + +unsafe impl Send for Decrypter {} +unsafe impl Sync for Decrypter {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "sodiumdecrypter", + gst::Rank::None, + Decrypter::static_type(), + ) +} diff --git a/generic/sodium/src/encrypter.rs b/generic/sodium/src/encrypter.rs deleted file mode 100644 index 0ba9b0e98..000000000 --- a/generic/sodium/src/encrypter.rs +++ /dev/null @@ -1,568 +0,0 @@ -// encrypter.rs -// -// Copyright 2019 Jordan Petridis -// -// 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, - sender_key: Option, - 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 { - 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 { - 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 { - 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, - state: Mutex>, -} - -impl Encrypter { - fn sink_chain( - &self, - pad: &gst::Pad, - element: &gst::Element, - buffer: gst::Buffer, - ) -> Result { - 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::Caps::new(&caps)) - } - 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::Duration::new(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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - let templ = klass.get_pad_template("sink").unwrap(); - let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) - .chain_function(|pad, parent, buffer| { - Encrypter::catch_panic_pad_function( - parent, - || Err(gst::FlowError::Error), - |encrypter, element| encrypter.sink_chain(pad, element, buffer), - ) - }) - .event_function(|pad, parent, event| { - Encrypter::catch_panic_pad_function( - parent, - || false, - |encrypter, element| encrypter.sink_event(pad, element, event), - ) - }) - .build(); - - let templ = klass.get_pad_template("src").unwrap(); - let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) - .query_function(|pad, parent, query| { - Encrypter::catch_panic_pad_function( - parent, - || false, - |encrypter, element| encrypter.src_query(pad, element, query), - ) - }) - .event_function(|pad, parent, event| { - Encrypter::catch_panic_pad_function( - parent, - || false, - |encrypter, element| encrypter.src_event(pad, element, event), - ) - }) - .build(); - - let props = Mutex::new(Props::default()); - let state = Mutex::new(None); - - Self { - srcpad, - sinkpad, - props, - state, - } - } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Encrypter", - "Generic", - "libsodium-based file encrypter", - "Jordan Petridis ", - ); - - 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 { - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let element = obj.downcast_ref::().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 { - 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_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/sodium/src/encrypter/imp.rs b/generic/sodium/src/encrypter/imp.rs new file mode 100644 index 000000000..e17868ec7 --- /dev/null +++ b/generic/sodium/src/encrypter/imp.rs @@ -0,0 +1,564 @@ +// encrypter.rs +// +// Copyright 2019 Jordan Petridis +// +// 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, + sender_key: Option, + 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 { + 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 { + 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 { + 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) + } +} + +pub struct Encrypter { + srcpad: gst::Pad, + sinkpad: gst::Pad, + props: Mutex, + state: Mutex>, +} + +impl Encrypter { + fn sink_chain( + &self, + pad: &gst::Pad, + element: &super::Encrypter, + buffer: gst::Buffer, + ) -> Result { + 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: &super::Encrypter, 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::Caps::new(&caps)) + } + 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: &super::Encrypter, 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: &super::Encrypter, + 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::Duration::new(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 + crate::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 Type = super::Encrypter; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn with_class(klass: &Self::Class) -> Self { + let templ = klass.get_pad_template("sink").unwrap(); + let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) + .chain_function(|pad, parent, buffer| { + Encrypter::catch_panic_pad_function( + parent, + || Err(gst::FlowError::Error), + |encrypter, element| encrypter.sink_chain(pad, element, buffer), + ) + }) + .event_function(|pad, parent, event| { + Encrypter::catch_panic_pad_function( + parent, + || false, + |encrypter, element| encrypter.sink_event(pad, element, event), + ) + }) + .build(); + + let templ = klass.get_pad_template("src").unwrap(); + let srcpad = gst::Pad::builder_with_template(&templ, Some("src")) + .query_function(|pad, parent, query| { + Encrypter::catch_panic_pad_function( + parent, + || false, + |encrypter, element| encrypter.src_query(pad, element, query), + ) + }) + .event_function(|pad, parent, event| { + Encrypter::catch_panic_pad_function( + parent, + || false, + |encrypter, element| encrypter.src_event(pad, element, event), + ) + }) + .build(); + + let props = Mutex::new(Props::default()); + let state = Mutex::new(None); + + Self { + srcpad, + sinkpad, + props, + state, + } + } + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Encrypter", + "Generic", + "libsodium-based file encrypter", + "Jordan Petridis ", + ); + + 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 { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(&self.sinkpad).unwrap(); + obj.add_pad(&self.srcpad).unwrap(); + } + + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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) + } +} diff --git a/generic/sodium/src/encrypter/mod.rs b/generic/sodium/src/encrypter/mod.rs new file mode 100644 index 000000000..b71dd3f91 --- /dev/null +++ b/generic/sodium/src/encrypter/mod.rs @@ -0,0 +1,43 @@ +// encrypter.rs +// +// Copyright 2019 Jordan Petridis +// +// 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::*; + +mod imp; + +glib_wrapper! { + pub struct Encrypter(ObjectSubclass) @extends gst::Element, gst::Object; +} + +unsafe impl Send for Encrypter {} +unsafe impl Sync for Encrypter {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "sodiumencrypter", + gst::Rank::None, + Encrypter::static_type(), + ) +} diff --git a/generic/threadshare/src/appsrc.rs b/generic/threadshare/src/appsrc.rs deleted file mode 100644 index ae6f7b9a1..000000000 --- a/generic/threadshare/src/appsrc.rs +++ /dev/null @@ -1,746 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// Copyright (C) 2019-2020 François Laignel -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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::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, PadSrcWeak, Task, TaskState}; - -const DEFAULT_CONTEXT: &str = ""; -const DEFAULT_CONTEXT_WAIT: u32 = 0; -const DEFAULT_CAPS: Option = 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, - 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, -} - -impl Default for AppSrcPadHandlerState { - fn default() -> Self { - AppSrcPadHandlerState { - need_initial_events: true, - need_segment: true, - caps: None, - } - } -} - -#[derive(Debug, Default)] -struct AppSrcPadHandlerInner { - state: FutMutex, - configured_caps: StdMutex>, -} - -#[derive(Clone, Debug, Default)] -struct AppSrcPadHandler(Arc); - -impl AppSrcPadHandler { - fn prepare(&self, caps: Option) { - self.0 - .state - .try_lock() - .expect("State locked elsewhere") - .caps = caps; - } - - async fn reset_state(&self) { - *self.0.state.lock().await = Default::default(); - } - - async fn set_need_segment(&self) { - self.0.state.lock().await.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::(), rand::random::()); - let stream_start_evt = gst::event::StreamStart::builder(&stream_id) - .group_id(gst::GroupId::next()) - .build(); - pad.push_event(stream_start_evt).await; - - if let Some(ref caps) = state.caps { - pad.push_event(gst::event::Caps::new(&caps)).await; - *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); - } - - state.need_initial_events = false; - } - - if state.need_segment { - let segment_evt = - gst::event::Segment::new(&gst::FormattedSegment::::new()); - pad.push_event(segment_evt).await; - - state.need_segment = false; - } - } - - async fn push_item( - &self, - pad: &PadSrcRef<'_>, - element: &gst::Element, - item: StreamItem, - ) -> Result { - 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) => { - match event.view() { - gst::EventView::Eos(_) => { - // Let the caller push the event - Err(gst::FlowError::Eos) - } - _ => { - 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.task.flush_start().is_ok(), - EventView::FlushStop(..) => appsrc.task.flush_stop().is_ok(), - 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)] -struct AppSrcTask { - element: gst::Element, - src_pad: PadSrcWeak, - src_pad_handler: AppSrcPadHandler, - receiver: mpsc::Receiver, -} - -impl AppSrcTask { - fn new( - element: &gst::Element, - src_pad: &PadSrc, - src_pad_handler: &AppSrcPadHandler, - receiver: mpsc::Receiver, - ) -> Self { - AppSrcTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - src_pad_handler: src_pad_handler.clone(), - receiver, - } - } -} - -impl AppSrcTask { - fn flush(&mut self) { - // Purge the channel - while let Ok(Some(_item)) = self.receiver.try_next() {} - } -} - -impl TaskImpl for AppSrcTask { - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = match self.receiver.next().await { - Some(item) => item, - None => { - gst_error!(CAT, obj: &self.element, "SrcPad channel aborted"); - gst_element_error!( - &self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason: channel aborted"] - ); - return Err(gst::FlowError::Flushing); - } - }; - - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let res = self - .src_pad_handler - .push_item(&pad, &self.element, item) - .await; - match res { - Ok(_) => { - gst_log!(CAT, obj: &self.element, "Successfully pushed item"); - } - Err(gst::FlowError::Eos) => { - gst_debug!(CAT, obj: &self.element, "EOS"); - pad.push_event(gst::event::Eos::new()).await; - } - Err(gst::FlowError::Flushing) => { - gst_debug!(CAT, obj: &self.element, "Flushing"); - } - Err(err) => { - gst_error!(CAT, obj: &self.element, "Got error {}", err); - gst_element_error!( - &self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - } - } - - res.map(drop) - } - .boxed() - } - - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task"); - - self.flush(); - self.src_pad_handler.reset_state().await; - - gst_log!(CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() - } - - fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task flush"); - - self.flush(); - self.src_pad_handler.set_need_segment().await; - - gst_log!(CAT, obj: &self.element, "Task flush started"); - Ok(()) - } - .boxed() - } -} - -#[derive(Debug)] -struct AppSrc { - src_pad: PadSrc, - src_pad_handler: AppSrcPadHandler, - task: Task, - sender: StdMutex>>, - settings: StdMutex, -} - -impl AppSrc { - fn push_buffer(&self, element: &gst::Element, mut buffer: gst::Buffer) -> bool { - let state = self.task.lock_state(); - if *state != TaskState::Started && *state != TaskState::Paused { - gst_debug!(CAT, obj: element, "Rejecting buffer due to element 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, - }; - - match sender.try_send(StreamItem::Event(gst::event::Eos::new())) { - 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.src_pad_handler.prepare(settings.caps.clone()); - - self.task - .prepare( - AppSrcTask::new(element, &self.src_pad, &self.src_pad_handler, receiver), - context, - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_debug!(CAT, obj: element, "Prepared"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - - *self.sender.lock().unwrap() = None; - self.task.unprepare().unwrap(); - - gst_debug!(CAT, obj: element, "Unprepared"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) - } - - fn pause(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Pausing"); - self.task.pause()?; - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing app source", - "Source/Generic", - "Thread-sharing app source", - "Sebastian Dröge ", - ); - - 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::() - .expect("signal arg") - .expect("missing signal arg"); - let buffer = args[1] - .get::() - .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::() - .expect("signal arg") - .expect("missing signal arg"); - let appsrc = Self::from_instance(&element); - Some(appsrc.end_of_stream(&element).to_value()) - }, - ); - } - - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - let src_pad_handler = AppSrcPadHandler::default(); - - Self { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - src_pad_handler.clone(), - ), - src_pad_handler, - task: Task::default(), - sender: StdMutex::new(None), - settings: StdMutex::new(Settings::default()), - } - } -} - -impl ObjectImpl for AppSrc { - 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 { - 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::().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_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); - } - _ => (), - } - - 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/threadshare/src/appsrc/imp.rs b/generic/threadshare/src/appsrc/imp.rs new file mode 100644 index 000000000..85247392b --- /dev/null +++ b/generic/threadshare/src/appsrc/imp.rs @@ -0,0 +1,737 @@ +// Copyright (C) 2018 Sebastian Dröge +// Copyright (C) 2019-2020 François Laignel +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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::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, PadSrcWeak, Task, TaskState}; + +const DEFAULT_CONTEXT: &str = ""; +const DEFAULT_CONTEXT_WAIT: u32 = 0; +const DEFAULT_CAPS: Option = 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, + 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, +} + +impl Default for AppSrcPadHandlerState { + fn default() -> Self { + AppSrcPadHandlerState { + need_initial_events: true, + need_segment: true, + caps: None, + } + } +} + +#[derive(Debug, Default)] +struct AppSrcPadHandlerInner { + state: FutMutex, + configured_caps: StdMutex>, +} + +#[derive(Clone, Debug, Default)] +struct AppSrcPadHandler(Arc); + +impl AppSrcPadHandler { + fn prepare(&self, caps: Option) { + self.0 + .state + .try_lock() + .expect("State locked elsewhere") + .caps = caps; + } + + async fn reset_state(&self) { + *self.0.state.lock().await = Default::default(); + } + + async fn set_need_segment(&self) { + self.0.state.lock().await.need_segment = true; + } + + async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &super::AppSrc) { + 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::(), rand::random::()); + let stream_start_evt = gst::event::StreamStart::builder(&stream_id) + .group_id(gst::GroupId::next()) + .build(); + pad.push_event(stream_start_evt).await; + + if let Some(ref caps) = state.caps { + pad.push_event(gst::event::Caps::new(&caps)).await; + *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); + } + + state.need_initial_events = false; + } + + if state.need_segment { + let segment_evt = + gst::event::Segment::new(&gst::FormattedSegment::::new()); + pad.push_event(segment_evt).await; + + state.need_segment = false; + } + } + + async fn push_item( + &self, + pad: &PadSrcRef<'_>, + element: &super::AppSrc, + item: StreamItem, + ) -> Result { + 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) => { + match event.view() { + gst::EventView::Eos(_) => { + // Let the caller push the event + Err(gst::FlowError::Eos) + } + _ => { + 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.task.flush_start().is_ok(), + EventView::FlushStop(..) => appsrc.task.flush_stop().is_ok(), + 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)] +struct AppSrcTask { + element: super::AppSrc, + src_pad: PadSrcWeak, + src_pad_handler: AppSrcPadHandler, + receiver: mpsc::Receiver, +} + +impl AppSrcTask { + fn new( + element: &super::AppSrc, + src_pad: &PadSrc, + src_pad_handler: &AppSrcPadHandler, + receiver: mpsc::Receiver, + ) -> Self { + AppSrcTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + src_pad_handler: src_pad_handler.clone(), + receiver, + } + } +} + +impl AppSrcTask { + fn flush(&mut self) { + // Purge the channel + while let Ok(Some(_item)) = self.receiver.try_next() {} + } +} + +impl TaskImpl for AppSrcTask { + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = match self.receiver.next().await { + Some(item) => item, + None => { + gst_error!(CAT, obj: &self.element, "SrcPad channel aborted"); + gst_element_error!( + &self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason: channel aborted"] + ); + return Err(gst::FlowError::Flushing); + } + }; + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let res = self + .src_pad_handler + .push_item(&pad, &self.element, item) + .await; + match res { + Ok(_) => { + gst_log!(CAT, obj: &self.element, "Successfully pushed item"); + } + Err(gst::FlowError::Eos) => { + gst_debug!(CAT, obj: &self.element, "EOS"); + pad.push_event(gst::event::Eos::new()).await; + } + Err(gst::FlowError::Flushing) => { + gst_debug!(CAT, obj: &self.element, "Flushing"); + } + Err(err) => { + gst_error!(CAT, obj: &self.element, "Got error {}", err); + gst_element_error!( + &self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + } + } + + res.map(drop) + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task"); + + self.flush(); + self.src_pad_handler.reset_state().await; + + gst_log!(CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } + + fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task flush"); + + self.flush(); + self.src_pad_handler.set_need_segment().await; + + gst_log!(CAT, obj: &self.element, "Task flush started"); + Ok(()) + } + .boxed() + } +} + +#[derive(Debug)] +pub struct AppSrc { + src_pad: PadSrc, + src_pad_handler: AppSrcPadHandler, + task: Task, + sender: StdMutex>>, + settings: StdMutex, +} + +impl AppSrc { + fn push_buffer(&self, element: &super::AppSrc, mut buffer: gst::Buffer) -> bool { + let state = self.task.lock_state(); + if *state != TaskState::Started && *state != TaskState::Paused { + gst_debug!(CAT, obj: element, "Rejecting buffer due to element 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: &super::AppSrc) -> bool { + let mut sender = self.sender.lock().unwrap(); + let sender = match sender.as_mut() { + Some(sender) => sender, + None => return false, + }; + + match sender.try_send(StreamItem::Event(gst::event::Eos::new())) { + Ok(_) => true, + Err(err) => { + gst_error!(CAT, obj: element, "Failed to queue EOS: {}", err); + false + } + } + } + + fn prepare(&self, element: &super::AppSrc) -> 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.src_pad_handler.prepare(settings.caps.clone()); + + self.task + .prepare( + AppSrcTask::new(element, &self.src_pad, &self.src_pad_handler, receiver), + context, + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_debug!(CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::AppSrc) { + gst_debug!(CAT, obj: element, "Unpreparing"); + + *self.sender.lock().unwrap() = None; + self.task.unprepare().unwrap(); + + gst_debug!(CAT, obj: element, "Unprepared"); + } + + fn stop(&self, element: &super::AppSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::AppSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } + + fn pause(&self, element: &super::AppSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Pausing"); + self.task.pause()?; + gst_debug!(CAT, obj: element, "Paused"); + Ok(()) + } +} + +impl ObjectSubclass for AppSrc { + const NAME: &'static str = "RsTsAppSrc"; + type Type = super::AppSrc; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing app source", + "Source/Generic", + "Thread-sharing app source", + "Sebastian Dröge ", + ); + + 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::() + .expect("signal arg") + .expect("missing signal arg"); + let buffer = args[1] + .get::() + .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::() + .expect("signal arg") + .expect("missing signal arg"); + let appsrc = Self::from_instance(&element); + Some(appsrc.end_of_stream(&element).to_value()) + }, + ); + } + + fn with_class(klass: &Self::Class) -> Self { + let src_pad_handler = AppSrcPadHandler::default(); + + Self { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + src_pad_handler.clone(), + ), + src_pad_handler, + task: Task::default(), + sender: StdMutex::new(None), + settings: StdMutex::new(Settings::default()), + } + } +} + +impl ObjectImpl for AppSrc { + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SOURCE); + } +} + +impl ElementImpl for AppSrc { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + 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) + } +} diff --git a/generic/threadshare/src/appsrc/mod.rs b/generic/threadshare/src/appsrc/mod.rs new file mode 100644 index 000000000..6f1e17296 --- /dev/null +++ b/generic/threadshare/src/appsrc/mod.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Sebastian Dröge +// Copyright (C) 2019-2020 François Laignel +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct AppSrc(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for AppSrc {} +unsafe impl Sync for AppSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-appsrc", + gst::Rank::None, + AppSrc::static_type(), + ) +} diff --git a/generic/threadshare/src/inputselector.rs b/generic/threadshare/src/inputselector.rs deleted file mode 100644 index 295d4537e..000000000 --- a/generic/threadshare/src/inputselector.rs +++ /dev/null @@ -1,640 +0,0 @@ -// Copyright (C) 2020 Mathieu Duponchelle -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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::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, - send_sticky: bool, - abort_handle: Option, -} - -impl Default for InputSelectorPadSinkHandlerInner { - fn default() -> Self { - InputSelectorPadSinkHandlerInner { - segment: None, - send_sticky: true, - abort_handle: None, - } - } -} - -#[derive(Clone, Debug, Default)] -struct InputSelectorPadSinkHandler(Arc>); - -impl InputSelectorPadSinkHandler { - /* Wait until specified time */ - async fn sync(&self, element: &gst::Element, running_time: gst::ClockTime) { - let now = element.get_current_running_time(); - - if let Some(delay) = running_time - .saturating_sub(now) - .and_then(|delay| delay.nseconds()) - { - runtime::time::delay_for(Duration::from_nanos(delay)).await; - } - } - - async fn handle_item( - &self, - pad: &PadSinkRef<'_>, - element: &gst::Element, - mut buffer: gst::Buffer, - ) -> Result { - 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::() { - 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> { - 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> { - 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 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::ClockTime::none(); - let pads = { - let pads = inputselector.pads.lock().unwrap(); - pads.sink_pads - .iter() - .map(|p| p.0.clone()) - .collect::>() - }; - - for pad in pads { - let mut peer_query = gst::query::Latency::new(); - - ret = pad.peer_query(&mut peer_query); - - if ret { - let (live, min, max) = peer_query.get_result(); - if live { - min_latency = min.max(min_latency).unwrap_or(min_latency); - max_latency = max.min(max_latency).unwrap_or(max); - } - } - } - - 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, - 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, -} - -impl Default for Pads { - fn default() -> Pads { - Pads { - pad_serial: 0, - sink_pads: HashMap::new(), - } - } -} - -#[derive(Debug)] -struct InputSelector { - src_pad: PadSrc, - state: Mutex, - settings: Mutex, - pads: Mutex, -} - -lazy_static! { - static ref CAT: gst::DebugCategory = gst::DebugCategory::new( - "ts-input-selector", - gst::DebugColorFlags::empty(), - Some("Thread-sharing input selector"), - ); -} - -impl InputSelector { - fn unprepare(&self, element: &gst::Element) -> Result<(), ()> { - let mut state = self.state.lock().unwrap(); - gst_debug!(CAT, obj: element, "Unpreparing"); - *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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing input selector", - "Generic", - "Simple input selector element", - "Mathieu Duponchelle ", - ); - - 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - Self { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - InputSelectorPadSrcHandler, - ), - state: Mutex::new(State::default()), - settings: Mutex::new(Settings::default()), - pads: Mutex::new(Pads::default()), - } - } -} - -impl ObjectImpl for InputSelector { - 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::().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::Reconfigure::new()); - } - } - - if let Some(pad) = pad { - let _ = pad.push_event(gst::event::Reconfigure::new()); - } - } - _ => unimplemented!(), - } - } - - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - 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::().unwrap(); - element.add_pad(self.src_pad.gst_pad()).unwrap(); - element - .set_element_flags(gst::ElementFlags::PROVIDE_CLOCK | gst::ElementFlags::REQUIRE_CLOCK); - } -} - -impl ElementImpl for InputSelector { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - gst_trace!(CAT, obj: element, "Changing state {:?}", transition); - - if let gst::StateChange::ReadyToNull = transition { - 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, - _caps: Option<&gst::Caps>, - ) -> Option { - let mut state = self.state.lock().unwrap(); - let mut pads = self.pads.lock().unwrap(); - let sink_pad = - gst::Pad::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, InputSelectorPadSinkHandler::default()); - let ret = sink_pad.gst_pad().clone(); - - 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::Latency::builder().src(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(); - drop(sink_pad); - element.remove_pad(pad).unwrap(); - drop(pads); - - let _ = element.post_message(gst::message::Latency::builder().src(element).build()); - } - - fn provide_clock(&self, _element: &gst::Element) -> Option { - Some(gst::SystemClock::obtain()) - } -} - -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/threadshare/src/inputselector/imp.rs b/generic/threadshare/src/inputselector/imp.rs new file mode 100644 index 000000000..914e4f39f --- /dev/null +++ b/generic/threadshare/src/inputselector/imp.rs @@ -0,0 +1,630 @@ +// Copyright (C) 2020 Mathieu Duponchelle +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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::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, + send_sticky: bool, + abort_handle: Option, +} + +impl Default for InputSelectorPadSinkHandlerInner { + fn default() -> Self { + InputSelectorPadSinkHandlerInner { + segment: None, + send_sticky: true, + abort_handle: None, + } + } +} + +#[derive(Clone, Debug, Default)] +struct InputSelectorPadSinkHandler(Arc>); + +impl InputSelectorPadSinkHandler { + /* Wait until specified time */ + async fn sync(&self, element: &super::InputSelector, running_time: gst::ClockTime) { + let now = element.get_current_running_time(); + + if let Some(delay) = running_time + .saturating_sub(now) + .and_then(|delay| delay.nseconds()) + { + runtime::time::delay_for(Duration::from_nanos(delay)).await; + } + } + + async fn handle_item( + &self, + pad: &PadSinkRef<'_>, + element: &super::InputSelector, + mut buffer: gst::Buffer, + ) -> Result { + 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::() { + 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> { + let this = self.clone(); + let element = element.clone().downcast::().unwrap(); + 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> { + let this = self.clone(); + let element = element.clone().downcast::().unwrap(); + 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 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::ClockTime::none(); + let pads = { + let pads = inputselector.pads.lock().unwrap(); + pads.sink_pads + .iter() + .map(|p| p.0.clone()) + .collect::>() + }; + + for pad in pads { + let mut peer_query = gst::query::Latency::new(); + + ret = pad.peer_query(&mut peer_query); + + if ret { + let (live, min, max) = peer_query.get_result(); + if live { + min_latency = min.max(min_latency).unwrap_or(min_latency); + max_latency = max.min(max_latency).unwrap_or(max); + } + } + } + + 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, + 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, +} + +impl Default for Pads { + fn default() -> Pads { + Pads { + pad_serial: 0, + sink_pads: HashMap::new(), + } + } +} + +#[derive(Debug)] +pub struct InputSelector { + src_pad: PadSrc, + state: Mutex, + settings: Mutex, + pads: Mutex, +} + +lazy_static! { + static ref CAT: gst::DebugCategory = gst::DebugCategory::new( + "ts-input-selector", + gst::DebugColorFlags::empty(), + Some("Thread-sharing input selector"), + ); +} + +impl InputSelector { + fn unprepare(&self, element: &super::InputSelector) -> Result<(), ()> { + let mut state = self.state.lock().unwrap(); + gst_debug!(CAT, obj: element, "Unpreparing"); + *state = State::default(); + gst_debug!(CAT, obj: element, "Unprepared"); + + Ok(()) + } +} + +impl ObjectSubclass for InputSelector { + const NAME: &'static str = "RsTsInputSelector"; + type Type = super::InputSelector; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing input selector", + "Generic", + "Simple input selector element", + "Mathieu Duponchelle ", + ); + + 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 with_class(klass: &Self::Class) -> Self { + Self { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + InputSelectorPadSrcHandler, + ), + state: Mutex::new(State::default()), + settings: Mutex::new(Settings::default()), + pads: Mutex::new(Pads::default()), + } + } +} + +impl ObjectImpl for InputSelector { + fn set_property(&self, _obj: &Self::Type, 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::().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::Reconfigure::new()); + } + } + + if let Some(pad) = pad { + let _ = pad.push_event(gst::event::Reconfigure::new()); + } + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + obj.set_element_flags(gst::ElementFlags::PROVIDE_CLOCK | gst::ElementFlags::REQUIRE_CLOCK); + } +} + +impl ElementImpl for InputSelector { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + gst_trace!(CAT, obj: element, "Changing state {:?}", transition); + + if let gst::StateChange::ReadyToNull = transition { + 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: &Self::Type, + templ: &gst::PadTemplate, + _name: Option, + _caps: Option<&gst::Caps>, + ) -> Option { + let mut state = self.state.lock().unwrap(); + let mut pads = self.pads.lock().unwrap(); + let sink_pad = + gst::Pad::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, InputSelectorPadSinkHandler::default()); + let ret = sink_pad.gst_pad().clone(); + + 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::Latency::builder().src(element).build()); + + Some(ret) + } + + fn release_pad(&self, element: &Self::Type, pad: &gst::Pad) { + let mut pads = self.pads.lock().unwrap(); + let sink_pad = pads.sink_pads.remove(pad).unwrap(); + drop(sink_pad); + element.remove_pad(pad).unwrap(); + drop(pads); + + let _ = element.post_message(gst::message::Latency::builder().src(element).build()); + } + + fn provide_clock(&self, _element: &Self::Type) -> Option { + Some(gst::SystemClock::obtain()) + } +} diff --git a/generic/threadshare/src/inputselector/mod.rs b/generic/threadshare/src/inputselector/mod.rs new file mode 100644 index 000000000..1b8d9e73d --- /dev/null +++ b/generic/threadshare/src/inputselector/mod.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2020 Mathieu Duponchelle +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct InputSelector(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for InputSelector {} +unsafe impl Sync for InputSelector {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-input-selector", + gst::Rank::None, + InputSelector::static_type(), + ) +} diff --git a/generic/threadshare/src/jitterbuffer/ffi.rs b/generic/threadshare/src/jitterbuffer/ffi.rs new file mode 100644 index 000000000..46424fa14 --- /dev/null +++ b/generic/threadshare/src/jitterbuffer/ffi.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2019 Sebastian Dröge +// +// 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_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; +} diff --git a/generic/threadshare/src/jitterbuffer/imp.rs b/generic/threadshare/src/jitterbuffer/imp.rs new file mode 100644 index 000000000..895efbc06 --- /dev/null +++ b/generic/threadshare/src/jitterbuffer/imp.rs @@ -0,0 +1,1636 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst::{gst_debug, gst_element_error, 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::runtime::prelude::*; +use crate::runtime::{self, Context, PadSink, PadSinkRef, PadSrc, PadSrcRef, Task}; + +use super::jitterbuffer::{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 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 { + 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, + ips_pts: gst::ClockTime, + + gap_packets: BTreeSet, + + last_pt: Option, + + last_in_seqnum: Option, + last_rtptime: Option, +} + +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, Default)] +struct SinkHandler(Arc>); + +impl SinkHandler { + 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: &super::JitterBuffer, + ) -> BTreeSet { + 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: &super::JitterBuffer, + caps: &gst::Caps, + pt: u8, + ) -> Result { + let s = caps.get_structure(0).ok_or(gst::FlowError::Error)?; + + gst_info!(CAT, obj: element, "Parsing {:?}", caps); + + let payload = s + .get_some::("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::("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; + + assert!(old_packet_spacing.is_some()); + if old_packet_spacing > new_packet_spacing { + state.packet_spacing = (new_packet_spacing + 3 * old_packet_spacing) / 4; + } else if !old_packet_spacing.is_zero() { + 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: &super::JitterBuffer, + 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: &super::JitterBuffer, + buffer: gst::Buffer, + ) -> Result { + 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 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.is_none() { + dts = pts; + } else if pts.is_none() { + pts = dts; + } + + if dts.is_none() { + dts = element.get_current_running_time(); + 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); + } + } + + let mut state = { + if state.clock_rate.is_none() { + drop(state); + let caps = element + .emit("request-pt-map", &[&(pt as u32)]) + .map_err(|_| gst::FlowError::Error)? + .ok_or(gst::FlowError::Error)? + .get::() + .map_err(|_| gst::FlowError::Error)? + .ok_or(gst::FlowError::Error)?; + let mut state = jb.state.lock().unwrap(); + self.parse_caps(inner, &mut state, element, &caps, pt)?; + state + } else { + state + } + }; + + 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: &super::JitterBuffer, + buffer: Option, + ) -> Result { + 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> { + let pad_weak = pad.downgrade(); + let element = element.clone().downcast::().unwrap(); + 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() { + if let Err(err) = jb.task.flush_start() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStart failed {:?}", err] + ); + return false; + } + } + + 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().downcast::().unwrap(); + + 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::() + .unwrap(); + } + EventView::FlushStop(..) => { + if let Err(err) = jb.task.flush_stop() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStop failed {:?}", err] + ); + return false; + } + } + 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, Default)] +struct SrcHandler; + +impl SrcHandler { + fn clear(&self) {} + + fn generate_lost_events( + &self, + state: &mut State, + element: &super::JitterBuffer, + seqnum: u16, + pts: gst::ClockTime, + discont: &mut bool, + ) -> Vec { + 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::CustomDownstream::new(s)); + } + + 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", ×tamp), + ("duration", &duration), + ("retry", &0), + ], + ); + + events.push(gst::event::CustomDownstream::new(s)); + } + + state.stats.num_lost += 1; + + lost_seqnum = lost_seqnum.wrapping_add(1); + } + } + + events + } + + async fn pop_and_push( + &self, + element: &super::JitterBuffer, + ) -> Result { + 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: &super::JitterBuffer, + state: &State, + latency: gst::ClockTime, + context_wait: gst::ClockTime, + ) -> (gst::ClockTime, Option<(gst::ClockTime, Duration)>) { + let now = element.get_current_running_time(); + + 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 = next_wakeup + .saturating_sub(now) + .unwrap_or_else(gst::ClockTime::zero) + .nseconds() + .unwrap(); + + 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(..) => { + if let Err(err) = jb.task.flush_start() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStart failed {:?}", err] + ); + return false; + } + } + EventView::FlushStop(..) => { + if let Err(err) = jb.task.flush_stop() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStop failed {:?}", err] + ); + return false; + } + } + _ => (), + } + + 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::Latency::new(); + + 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, + + last_res: Result, + position: gst::ClockTime, + + segment: gst::FormattedSegment, + clock_rate: Option, + + packet_spacing: gst::ClockTime, + equidistant: i32, + + discont: bool, + eos: bool, + + last_popped_seqnum: Option, + last_popped_pts: gst::ClockTime, + + stats: Stats, + + earliest_pts: gst::ClockTime, + earliest_seqnum: Option, + + 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::::new(), + clock_rate: None, + + packet_spacing: gst::ClockTime::zero(), + 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 JitterBufferTask { + element: super::JitterBuffer, + src_pad_handler: SrcHandler, + sink_pad_handler: SinkHandler, +} + +impl JitterBufferTask { + fn new( + element: &super::JitterBuffer, + src_pad_handler: &SrcHandler, + sink_pad_handler: &SinkHandler, + ) -> Self { + JitterBufferTask { + element: element.clone(), + src_pad_handler: src_pad_handler.clone(), + sink_pad_handler: sink_pad_handler.clone(), + } + } +} + +impl TaskImpl for JitterBufferTask { + fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task"); + + self.src_pad_handler.clear(); + self.sink_pad_handler.clear(); + + let jb = JitterBuffer::from_instance(&self.element); + *jb.state.lock().unwrap() = State::default(); + + gst_log!(CAT, obj: &self.element, "Task started"); + Ok(()) + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let jb = JitterBuffer::from_instance(&self.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) = self.src_pad_handler.get_next_wakeup( + &self.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: &self.element, "Waiting"); + if let Err(Aborted) = delay_fut.await { + gst_debug!(CAT, obj: &self.element, "Waiting aborted"); + return Ok(()); + } + } + + 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) = self.src_pad_handler.get_next_wakeup( + &self.element, + &state, + latency, + context_wait, + ); + + gst_debug!( + CAT, + obj: &self.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 Ok(()); + } + } else { + return Ok(()); + } + + let (head_pts, head_seq) = state.jbuf.borrow().peek(); + + (head_pts, head_seq) + }; + + let res = self.src_pad_handler.pop_and_push(&self.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) = self.src_pad_handler.get_next_wakeup( + &self.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 Ok(()); + } + } else { + return Ok(()); + } + } + } + + if let Err(err) = res { + match err { + gst::FlowError::Eos => { + gst_debug!(CAT, obj: &self.element, "Pushing EOS event"); + let _ = jb.src_pad.push_event(gst::event::Eos::new()).await; + } + gst::FlowError::Flushing => gst_debug!(CAT, obj: &self.element, "Flushing"), + err => gst_error!(CAT, obj: &self.element, "Error {}", err), + } + + return Err(err); + } + } + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task"); + + let jb = JitterBuffer::from_instance(&self.element); + let mut jb_state = jb.state.lock().unwrap(); + + if let Some((_, abort_handle)) = jb_state.wait_handle.take() { + abort_handle.abort(); + } + + self.src_pad_handler.clear(); + self.sink_pad_handler.clear(); + + *jb_state = State::default(); + + gst_log!(CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } +} + +pub struct JitterBuffer { + sink_pad: PadSink, + src_pad: PadSrc, + sink_pad_handler: SinkHandler, + src_pad_handler: SrcHandler, + task: Task, + state: StdMutex, + settings: StdMutex, +} + +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: &super::JitterBuffer) { + 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: &super::JitterBuffer) -> 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( + JitterBufferTask::new(element, &self.src_pad_handler, &self.sink_pad_handler), + context, + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_info!(CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::JitterBuffer) { + gst_debug!(CAT, obj: element, "Unpreparing"); + self.task.unprepare().unwrap(); + gst_debug!(CAT, obj: element, "Unprepared"); + } + + fn start(&self, element: &super::JitterBuffer) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } + + fn stop(&self, element: &super::JitterBuffer) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } +} + +impl ObjectSubclass for JitterBuffer { + const NAME: &'static str = "RsTsJitterBuffer"; + type Type = super::JitterBuffer; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing jitterbuffer", + "Generic", + "Simple jitterbuffer", + "Mathieu Duponchelle ", + ); + + 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::() + .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 with_class(klass: &Self::Class) -> Self { + let sink_pad_handler = SinkHandler::default(); + let src_pad_handler = SrcHandler::default(); + + Self { + sink_pad: PadSink::new( + gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), + sink_pad_handler.clone(), + ), + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + src_pad_handler.clone(), + ), + sink_pad_handler, + src_pad_handler, + task: Task::default(), + state: StdMutex::new(State::default()), + settings: StdMutex::new(Settings::default()), + } + } +} + +impl ObjectImpl for JitterBuffer { + fn set_property(&self, obj: &Self::Type, 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 _ = obj.post_message(gst::message::Latency::builder().src(obj).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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.sink_pad.gst_pad()).unwrap(); + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + obj.set_element_flags(gst::ElementFlags::PROVIDE_CLOCK | gst::ElementFlags::REQUIRE_CLOCK); + } +} + +impl ElementImpl for JitterBuffer { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + let mut success = self.parent_change_state(element, transition)?; + + match transition { + gst::StateChange::ReadyToPaused => { + self.start(element).map_err(|_| gst::StateChangeError)?; + success = gst::StateChangeSuccess::NoPreroll; + } + gst::StateChange::PlayingToPaused => { + success = gst::StateChangeSuccess::NoPreroll; + } + _ => (), + } + + Ok(success) + } + + fn provide_clock(&self, _element: &Self::Type) -> Option { + Some(gst::SystemClock::obtain()) + } +} diff --git a/generic/threadshare/src/jitterbuffer/jitterbuffer.rs b/generic/threadshare/src/jitterbuffer/jitterbuffer.rs index 3579f9f3a..5984db98f 100644 --- a/generic/threadshare/src/jitterbuffer/jitterbuffer.rs +++ b/generic/threadshare/src/jitterbuffer/jitterbuffer.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Sebastian Dröge +// Copyright (C) 2019 Sebastian Dröge // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public @@ -15,1633 +15,369 @@ // 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 super::ffi; -use glib::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; +use std::ptr; -use gst::prelude::*; -use gst::subclass::prelude::*; -use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg, gst_info, gst_log, gst_trace}; -use gst_rtp::RTPBuffer; +use glib_sys as glib_ffi; +use gstreamer_sys as gst_ffi; -use lazy_static::lazy_static; +use glib::glib_wrapper; +use glib::prelude::*; +use glib::translate::*; -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::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; +glib_wrapper! { + pub struct RTPJitterBuffer(Object); -#[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, - } + match fn { + get_type => || ffi::rtp_jitter_buffer_get_type(), } } -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 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 } +unsafe impl glib::SendUnique for RTPJitterBuffer { + fn is_unique(&self) -> bool { + self.ref_count() == 1 } } -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 { - Some(self.cmp(other)) - } -} +impl ToGlib for RTPJitterBufferMode { + type GlibType = ffi::RTPJitterBufferMode; -impl PartialEq for GapPacket { - fn eq(&self, other: &Self) -> bool { - self.cmp(other) == Ordering::Equal + 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, + } } } -struct SinkHandlerInner { - packet_rate_ctx: RTPPacketRateCtx, - ips_rtptime: Option, - ips_pts: gst::ClockTime, - - gap_packets: BTreeSet, - - last_pt: Option, - - last_in_seqnum: Option, - last_rtptime: Option, -} - -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, +impl FromGlib 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), } } } -#[derive(Clone, Default)] -struct SinkHandler(Arc>); +pub struct RTPJitterBufferItem(Option>); -impl SinkHandler { - fn clear(&self) { - let mut inner = self.0.lock().unwrap(); - *inner = SinkHandlerInner::default(); - } +unsafe impl Send for RTPJitterBufferItem {} - // For resetting if seqnum discontinuities - fn reset( - &self, - inner: &mut SinkHandlerInner, - state: &mut State, - element: &gst::Element, - ) -> BTreeSet { - 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 { - let s = caps.get_structure(0).ok_or(gst::FlowError::Error)?; - - gst_info!(CAT, obj: element, "Parsing {:?}", caps); - - let payload = s - .get_some::("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::("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, +impl RTPJitterBufferItem { + pub fn new( + buffer: gst::Buffer, + dts: gst::ClockTime, 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; - - assert!(old_packet_spacing.is_some()); - if old_packet_spacing > new_packet_spacing { - state.packet_spacing = (new_packet_spacing + 3 * old_packet_spacing) / 4; - } else if !old_packet_spacing.is_zero() { - state.packet_spacing = (3 * new_packet_spacing + old_packet_spacing) / 4; - } else { - state.packet_spacing = new_packet_spacing; - } + seqnum: Option, + rtptime: u32, + ) -> RTPJitterBufferItem { + unsafe { + let ptr = ptr::NonNull::new(glib_sys::g_slice_alloc0(mem::size_of::< + ffi::RTPJitterBufferItem, + >()) as *mut ffi::RTPJitterBufferItem) + .expect("Allocation failed"); + ptr::write( + ptr.as_ptr(), + 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(std::u32::MAX), + count: 1, + rtptime, + }, + ); - 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; + RTPJitterBufferItem(Some(ptr)) } } - 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(); - } + pub fn into_buffer(mut self) -> gst::Buffer { + unsafe { + let item = self.0.take().expect("Invalid wrapper"); + let buf = item.as_ref().data as *mut gst_ffi::GstBuffer; + glib_sys::g_slice_free1( + mem::size_of::(), + item.as_ptr() as *mut _, + ); + from_glib_full(buf) } - - reset } - fn store( - &self, - inner: &mut SinkHandlerInner, - pad: &gst::Pad, - element: &gst::Element, - buffer: gst::Buffer, - ) -> Result { - 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 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.is_none() { - dts = pts; - } else if pts.is_none() { - pts = dts; - } - - if dts.is_none() { - dts = element.get_current_running_time(); - 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); - } - } - - let mut state = { - if state.clock_rate.is_none() { - drop(state); - let caps = element - .emit("request-pt-map", &[&(pt as u32)]) - .map_err(|_| gst::FlowError::Error)? - .ok_or(gst::FlowError::Error)? - .get::() - .map_err(|_| gst::FlowError::Error)? - .ok_or(gst::FlowError::Error)?; - let mut state = jb.state.lock().unwrap(); - self.parse_caps(inner, &mut state, element, &caps, pt)?; - state + pub fn get_dts(&self) -> gst::ClockTime { + unsafe { + let item = self.0.as_ref().expect("Invalid wrapper"); + if item.as_ref().dts == gst_ffi::GST_CLOCK_TIME_NONE { + gst::CLOCK_TIME_NONE } else { - state + gst::ClockTime(Some(item.as_ref().dts)) } - }; - - 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); + pub fn get_pts(&self) -> gst::ClockTime { + unsafe { + let item = self.0.as_ref().expect("Invalid wrapper"); + if item.as_ref().pts == gst_ffi::GST_CLOCK_TIME_NONE { + gst::CLOCK_TIME_NONE } 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; + gst::ClockTime(Some(item.as_ref().pts)) } - - 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, - ) -> Result { - 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; - } + pub fn get_seqnum(&self) -> Option { + unsafe { + let item = self.0.as_ref().expect("Invalid wrapper"); + if item.as_ref().seqnum == std::u32::MAX { + None + } else { + Some(item.as_ref().seqnum as u16) } } - 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> { - 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() { - if let Err(err) = jb.task.flush_start() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStart failed {:?}", err] - ); - return false; - } + #[allow(dead_code)] + pub fn get_rtptime(&self) -> u32 { + unsafe { + let item = self.0.as_ref().expect("Invalid wrapper"); + item.as_ref().rtptime } - - 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::() - .unwrap(); - } - EventView::FlushStop(..) => { - if let Err(err) = jb.task.flush_stop() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStop failed {:?}", err] - ); - return false; - } - } - 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; +impl Drop for RTPJitterBufferItem { + fn drop(&mut self) { + unsafe { + if let Some(ref item) = self.0 { + if !item.as_ref().data.is_null() { + gst_ffi::gst_mini_object_unref(item.as_ref().data as *mut _); } - _ => (), - }; - 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 + glib_sys::g_slice_free1( + mem::size_of::(), + item.as_ptr() as *mut _, + ); } } - .boxed() } } -#[derive(Clone, Default)] -struct SrcHandler; - -impl SrcHandler { - fn clear(&self) {} - - fn generate_lost_events( - &self, - state: &mut State, - element: &gst::Element, - seqnum: u16, - pts: gst::ClockTime, - discont: &mut bool, - ) -> Vec { - 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::CustomDownstream::new(s)); - } - - 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", ×tamp), - ("duration", &duration), - ("retry", &0), - ], - ); - - events.push(gst::event::CustomDownstream::new(s)); - } +pub struct RTPPacketRateCtx(Box); - state.stats.num_lost += 1; +unsafe impl Send for RTPPacketRateCtx {} - lost_seqnum = lost_seqnum.wrapping_add(1); - } +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())) } - - events } - async fn pop_and_push( - &self, - element: &gst::Element, - ) -> Result { - 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 + pub fn reset(&mut self, clock_rate: i32) { + unsafe { ffi::gst_rtp_packet_rate_ctx_reset(&mut *self.0, clock_rate) } } - 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 = element.get_current_running_time(); - - 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 = next_wakeup - .saturating_sub(now) - .unwrap_or_else(gst::ClockTime::zero) - .nseconds() - .unwrap(); - - gst_debug!( - CAT, - obj: element, - "Next wakeup at {} with delay {}", - next_wakeup, - delay - ); - - (now, Some((next_wakeup, Duration::from_nanos(delay)))) + pub fn update(&mut self, seqnum: u16, ts: u32) -> u32 { + unsafe { ffi::gst_rtp_packet_rate_ctx_update(&mut *self.0, seqnum, ts) } } -} -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(..) => { - if let Err(err) = jb.task.flush_start() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStart failed {:?}", err] - ); - return false; - } - } - EventView::FlushStop(..) => { - if let Err(err) = jb.task.flush_stop() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStop failed {:?}", err] - ); - return false; - } - } - _ => (), - } - - gst_log!(CAT, obj: pad.gst_pad(), "Forwarding {:?}", event); - jb.sink_pad.gst_pad().push_event(event) + 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) } } - 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::Latency::new(); - - 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), - } + #[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) } } } -#[derive(Debug)] -struct Stats { - num_pushed: u64, - num_lost: u64, - num_late: u64, -} - -impl Default for Stats { +impl Default for RTPPacketRateCtx { fn default() -> Self { - Self { - num_pushed: 0, - num_lost: 0, - num_late: 0, - } + RTPPacketRateCtx::new() } } -// Shared state between element, sink and source pad -struct State { - jbuf: glib::SendUniqueCell, - - last_res: Result, - position: gst::ClockTime, - - segment: gst::FormattedSegment, - clock_rate: Option, - - packet_spacing: gst::ClockTime, - equidistant: i32, - - discont: bool, - eos: bool, - - last_popped_seqnum: Option, - last_popped_pts: gst::ClockTime, - - stats: Stats, - - earliest_pts: gst::ClockTime, - earliest_seqnum: Option, - - wait_handle: Option<(gst::ClockTime, AbortHandle)>, +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +pub enum RTPJitterBufferMode { + r#None, + Slave, + Buffer, + Synced, + __Unknown(i32), } -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::::new(), - clock_rate: None, - - packet_spacing: gst::ClockTime::zero(), - 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, - } +impl RTPJitterBuffer { + pub fn new() -> RTPJitterBuffer { + unsafe { from_glib_full(ffi::rtp_jitter_buffer_new()) } } -} - -struct JitterBufferTask { - element: gst::Element, - src_pad_handler: SrcHandler, - sink_pad_handler: SinkHandler, -} -impl JitterBufferTask { - fn new( - element: &gst::Element, - src_pad_handler: &SrcHandler, - sink_pad_handler: &SinkHandler, - ) -> Self { - JitterBufferTask { - element: element.clone(), - src_pad_handler: src_pad_handler.clone(), - sink_pad_handler: sink_pad_handler.clone(), - } + #[allow(dead_code)] + pub fn get_mode(&self) -> RTPJitterBufferMode { + unsafe { from_glib(ffi::rtp_jitter_buffer_get_mode(self.to_glib_none().0)) } } -} -impl TaskImpl for JitterBufferTask { - fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task"); - - self.src_pad_handler.clear(); - self.sink_pad_handler.clear(); - - let jb = JitterBuffer::from_instance(&self.element); - *jb.state.lock().unwrap() = State::default(); - - gst_log!(CAT, obj: &self.element, "Task started"); - Ok(()) - } - .boxed() + #[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()) } } - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let jb = JitterBuffer::from_instance(&self.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) = self.src_pad_handler.get_next_wakeup( - &self.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: &self.element, "Waiting"); - if let Err(Aborted) = delay_fut.await { - gst_debug!(CAT, obj: &self.element, "Waiting aborted"); - return Ok(()); - } - } - - 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) = self.src_pad_handler.get_next_wakeup( - &self.element, - &state, - latency, - context_wait, - ); - - gst_debug!( - CAT, - obj: &self.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 Ok(()); - } - } else { - return Ok(()); - } - - let (head_pts, head_seq) = state.jbuf.borrow().peek(); - - (head_pts, head_seq) - }; - - let res = self.src_pad_handler.pop_and_push(&self.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) = self.src_pad_handler.get_next_wakeup( - &self.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 Ok(()); - } - } else { - return Ok(()); - } - } - } - - if let Err(err) = res { - match err { - gst::FlowError::Eos => { - gst_debug!(CAT, obj: &self.element, "Pushing EOS event"); - let _ = jb.src_pad.push_event(gst::event::Eos::new()).await; - } - gst::FlowError::Flushing => gst_debug!(CAT, obj: &self.element, "Flushing"), - err => gst_error!(CAT, obj: &self.element, "Error {}", err), - } - - return Err(err); - } - } - } - .boxed() + #[allow(dead_code)] + pub fn get_delay(&self) -> gst::ClockTime { + unsafe { from_glib(ffi::rtp_jitter_buffer_get_delay(self.to_glib_none().0)) } } - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task"); - - let jb = JitterBuffer::from_instance(&self.element); - let mut jb_state = jb.state.lock().unwrap(); - - if let Some((_, abort_handle)) = jb_state.wait_handle.take() { - abort_handle.abort(); - } - - self.src_pad_handler.clear(); - self.sink_pad_handler.clear(); - - *jb_state = State::default(); - - gst_log!(CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() + pub fn set_delay(&self, delay: gst::ClockTime) { + unsafe { ffi::rtp_jitter_buffer_set_delay(self.to_glib_none().0, delay.to_glib()) } } -} - -struct JitterBuffer { - sink_pad: PadSink, - src_pad: PadSrc, - sink_pad_handler: SinkHandler, - src_pad_handler: SrcHandler, - task: Task, - state: StdMutex, - settings: StdMutex, -} - -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(); + pub fn set_clock_rate(&self, clock_rate: u32) { + unsafe { ffi::rtp_jitter_buffer_set_clock_rate(self.to_glib_none().0, clock_rate) } } - 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( - JitterBufferTask::new(element, &self.src_pad_handler, &self.sink_pad_handler), - context, - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_info!(CAT, obj: element, "Prepared"); - - Ok(()) + #[allow(dead_code)] + pub fn get_clock_rate(&self) -> u32 { + unsafe { ffi::rtp_jitter_buffer_get_clock_rate(self.to_glib_none().0) } } - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - self.task.unprepare().unwrap(); - gst_debug!(CAT, obj: element, "Unprepared"); - } + 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(), + ); - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) + if pts == gst_ffi::GST_CLOCK_TIME_NONE { + gst::CLOCK_TIME_NONE + } else { + pts.into() + } + } } - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) + pub fn insert(&self, mut item: RTPJitterBufferItem) -> (bool, bool, i32) { + unsafe { + let mut head = mem::MaybeUninit::uninit(); + let mut percent = mem::MaybeUninit::uninit(); + let ptr = item.0.take().expect("Invalid wrapper"); + let ret: bool = from_glib(ffi::rtp_jitter_buffer_insert( + self.to_glib_none().0, + ptr.as_ptr(), + head.as_mut_ptr(), + percent.as_mut_ptr(), + )); + if !ret { + item.0 = Some(ptr); + } + (ret, from_glib(head.assume_init()), percent.assume_init()) + } } -} - -impl ObjectSubclass for JitterBuffer { - const NAME: &'static str = "RsTsJitterBuffer"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing jitterbuffer", - "Generic", - "Simple jitterbuffer", - "Mathieu Duponchelle ", - ); + pub fn find_earliest(&self) -> (gst::ClockTime, Option) { + unsafe { + let mut pts = mem::MaybeUninit::uninit(); + let mut seqnum = mem::MaybeUninit::uninit(); - 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(), - ); + 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(); - 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::() - .expect("signal arg") - .expect("missing signal arg"); - let jb = Self::from_instance(&element); - jb.clear_pt_map(&element); + let seqnum = if seqnum == std::u32::MAX { 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - let sink_pad_handler = SinkHandler::default(); - let src_pad_handler = SrcHandler::default(); + } else { + Some(seqnum as u16) + }; - Self { - sink_pad: PadSink::new( - gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), - sink_pad_handler.clone(), - ), - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - src_pad_handler.clone(), - ), - sink_pad_handler, - src_pad_handler, - task: Task::default(), - state: StdMutex::new(State::default()), - settings: StdMutex::new(Settings::default()), + if pts == gst_ffi::GST_CLOCK_TIME_NONE { + (gst::CLOCK_TIME_NONE, seqnum) + } else { + (pts.into(), seqnum) + } } } -} - -impl ObjectImpl for JitterBuffer { - 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); + pub fn pop(&self) -> (Option, i32) { + unsafe { + let mut percent = mem::MaybeUninit::uninit(); + let item = ffi::rtp_jitter_buffer_pop(self.to_glib_none().0, percent.as_mut_ptr()); - let element = obj.downcast_ref::().unwrap(); - let _ = element.post_message(gst::message::Latency::builder().src(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!(), + ( + if item.is_null() { + None + } else { + Some(RTPJitterBufferItem(Some(ptr::NonNull::new_unchecked(item)))) + }, + percent.assume_init(), + ) } } - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - 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()) + pub fn peek(&self) -> (gst::ClockTime, Option) { + 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 == std::u32::MAX { + None + } else { + Some(seqnum as u16) + }; + ((*item).pts.into(), seqnum) } - _ => unimplemented!(), } } - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let element = obj.downcast_ref::().unwrap(); - element.add_pad(self.sink_pad.gst_pad()).unwrap(); - element.add_pad(self.src_pad.gst_pad()).unwrap(); - element - .set_element_flags(gst::ElementFlags::PROVIDE_CLOCK | gst::ElementFlags::REQUIRE_CLOCK); - } -} - -impl ElementImpl for JitterBuffer { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - 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); - } - _ => (), + pub fn flush(&self) { + unsafe extern "C" fn free_item(item: glib_ffi::gpointer, _: glib_ffi::gpointer) { + let _ = + RTPJitterBufferItem(Some(ptr::NonNull::new(item as *mut _).expect("NULL item"))); } - let mut success = self.parent_change_state(element, transition)?; - - match transition { - gst::StateChange::ReadyToPaused => { - self.start(element).map_err(|_| gst::StateChangeError)?; - success = gst::StateChangeSuccess::NoPreroll; - } - gst::StateChange::PlayingToPaused => { - success = gst::StateChangeSuccess::NoPreroll; - } - _ => (), + unsafe { + ffi::rtp_jitter_buffer_flush(self.to_glib_none().0, Some(free_item)); } - - Ok(success) } - fn provide_clock(&self, _element: &gst::Element) -> Option { - Some(gst::SystemClock::obtain()) + pub fn reset_skew(&self) { + unsafe { ffi::rtp_jitter_buffer_reset_skew(self.to_glib_none().0) } } } -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ts-jitterbuffer", - gst::Rank::None, - JitterBuffer::get_type(), - ) +impl Default for RTPJitterBuffer { + fn default() -> Self { + RTPJitterBuffer::new() + } } diff --git a/generic/threadshare/src/jitterbuffer/mod.rs b/generic/threadshare/src/jitterbuffer/mod.rs index 01745aa77..0dc7d2806 100644 --- a/generic/threadshare/src/jitterbuffer/mod.rs +++ b/generic/threadshare/src/jitterbuffer/mod.rs @@ -15,472 +15,29 @@ // 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::ptr; -use std::u32; +use glib::prelude::*; +mod ffi; +mod imp; #[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::glib_wrapper; -use glib::prelude::*; -use glib::translate::*; - -use std::mem; glib_wrapper! { - pub struct RTPJitterBuffer(Object); - - 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 - } + pub struct JitterBuffer(ObjectSubclass) @extends gst::Element, gst::Object; } -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 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>); - -unsafe impl Send for RTPJitterBufferItem {} - -impl RTPJitterBufferItem { - pub fn new( - buffer: gst::Buffer, - dts: gst::ClockTime, - pts: gst::ClockTime, - seqnum: Option, - rtptime: u32, - ) -> RTPJitterBufferItem { - unsafe { - let ptr = ptr::NonNull::new(glib_sys::g_slice_alloc0(mem::size_of::< - ffi::RTPJitterBufferItem, - >()) as *mut ffi::RTPJitterBufferItem) - .expect("Allocation failed"); - ptr::write( - ptr.as_ptr(), - 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, - }, - ); - - RTPJitterBufferItem(Some(ptr)) - } - } - - pub fn into_buffer(mut self) -> gst::Buffer { - unsafe { - let item = self.0.take().expect("Invalid wrapper"); - let buf = item.as_ref().data as *mut gst_ffi::GstBuffer; - glib_sys::g_slice_free1( - mem::size_of::(), - item.as_ptr() as *mut _, - ); - from_glib_full(buf) - } - } - - pub fn get_dts(&self) -> gst::ClockTime { - unsafe { - let item = self.0.as_ref().expect("Invalid wrapper"); - if item.as_ref().dts == gst_ffi::GST_CLOCK_TIME_NONE { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime(Some(item.as_ref().dts)) - } - } - } - - pub fn get_pts(&self) -> gst::ClockTime { - unsafe { - let item = self.0.as_ref().expect("Invalid wrapper"); - if item.as_ref().pts == gst_ffi::GST_CLOCK_TIME_NONE { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime(Some(item.as_ref().pts)) - } - } - } - - pub fn get_seqnum(&self) -> Option { - unsafe { - let item = self.0.as_ref().expect("Invalid wrapper"); - if item.as_ref().seqnum == u32::MAX { - None - } else { - Some(item.as_ref().seqnum as u16) - } - } - } - - #[allow(dead_code)] - pub fn get_rtptime(&self) -> u32 { - unsafe { - let item = self.0.as_ref().expect("Invalid wrapper"); - item.as_ref().rtptime - } - } -} - -impl Drop for RTPJitterBufferItem { - fn drop(&mut self) { - unsafe { - if let Some(ref item) = self.0 { - if !item.as_ref().data.is_null() { - gst_ffi::gst_mini_object_unref(item.as_ref().data as *mut _); - } - - glib_sys::g_slice_free1( - mem::size_of::(), - item.as_ptr() as *mut _, - ); - } - } - } -} - -pub struct RTPPacketRateCtx(Box); - -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 ptr = item.0.take().expect("Invalid wrapper"); - let ret: bool = from_glib(ffi::rtp_jitter_buffer_insert( - self.to_glib_none().0, - ptr.as_ptr(), - head.as_mut_ptr(), - percent.as_mut_ptr(), - )); - if !ret { - item.0 = Some(ptr); - } - (ret, from_glib(head.assume_init()), percent.assume_init()) - } - } - - pub fn find_earliest(&self) -> (gst::ClockTime, Option) { - 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, 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(ptr::NonNull::new_unchecked(item)))) - }, - percent.assume_init(), - ) - } - } - - pub fn peek(&self) -> (gst::ClockTime, Option) { - 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(ptr::NonNull::new(item as *mut _).expect("NULL item"))); - } - - 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) } - } -} +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for JitterBuffer {} +unsafe impl Sync for JitterBuffer {} -impl Default for RTPJitterBuffer { - fn default() -> Self { - RTPJitterBuffer::new() - } +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-jitterbuffer", + gst::Rank::None, + JitterBuffer::static_type(), + ) } diff --git a/generic/threadshare/src/lib.rs b/generic/threadshare/src/lib.rs index af57fffc2..1264cbf56 100644 --- a/generic/threadshare/src/lib.rs +++ b/generic/threadshare/src/lib.rs @@ -53,7 +53,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { queue::register(plugin)?; proxy::register(plugin)?; appsrc::register(plugin)?; - jitterbuffer::jitterbuffer::register(plugin)?; + jitterbuffer::register(plugin)?; inputselector::register(plugin)?; Ok(()) diff --git a/generic/threadshare/src/proxy.rs b/generic/threadshare/src/proxy.rs deleted file mode 100644 index c524e9bbd..000000000 --- a/generic/threadshare/src/proxy.rs +++ /dev/null @@ -1,1334 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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}; - -lazy_static! { - static ref PROXY_CONTEXTS: StdMutex>>> = - StdMutex::new(HashMap::new()); - static ref PROXY_SRC_PADS: StdMutex> = - StdMutex::new(HashMap::new()); - static ref PROXY_SINK_PADS: StdMutex> = - 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>, - scheduled: bool, - items: VecDeque, -} - -impl PendingQueue { - fn notify_more_queue_space(&mut self) { - self.more_queue_space_sender.take(); - } -} - -#[derive(Debug)] -struct ProxyContextInner { - name: String, - dataqueue: Option, - last_res: Result, - pending_queue: Option, - 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>, - 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 { - 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> { - 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> { - 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); - } - - 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::Eos::builder().src(&element).build()); - } - EventView::FlushStop(..) => proxysink.start(&element), - _ => (), - } - - 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>, - settings: StdMutex, -} - -lazy_static! { - static ref SINK_CAT: gst::DebugCategory = gst::DebugCategory::new( - "ts-proxysink", - gst::DebugColorFlags::empty(), - Some("Thread-sharing proxy sink"), - ); -} - -impl ProxySink { - async fn schedule_pending_queue(&self, element: &gst::Element) { - loop { - let more_queue_space_receiver = { - let proxy_ctx = self.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 { - 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 { - gst_log!(SINK_CAT, obj: element, "Scheduling pending queue now"); - pending_queue.scheduled = true; - - let wait_fut = self.schedule_pending_queue(element); - 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); - - gst_debug!(SINK_CAT, obj: element, "Prepared"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(SINK_CAT, obj: element, "Unpreparing"); - *self.proxy_ctx.lock().unwrap() = None; - gst_debug!(SINK_CAT, obj: element, "Unprepared"); - } - - fn start(&self, element: &gst::Element) { - 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"); - } - - fn stop(&self, element: &gst::Element) { - 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"); - } -} - -impl ObjectSubclass for ProxySink { - const NAME: &'static str = "RsTsProxySink"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing proxy sink", - "Sink/Generic", - "Thread-sharing proxy sink", - "Sebastian Dröge ", - ); - - 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - Self { - sink_pad: PadSink::new( - gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), - ProxySinkPadHandler, - ), - proxy_ctx: StdMutex::new(None), - settings: StdMutex::new(SettingsSink::default()), - } - } -} - -impl ObjectImpl for ProxySink { - 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 { - 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::().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_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); - } - gst::StateChange::ReadyToNull => { - self.unprepare(element); - } - _ => (), - } - - let success = self.parent_change_state(element, transition)?; - - if transition == gst::StateChange::ReadyToPaused { - self.start(element); - } - - 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(pending_queue) = shared_ctx.pending_queue.as_mut() { - 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(..) => { - if let Err(err) = proxysrc.task.flush_start() { - gst_error!(SRC_CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStart failed {:?}", err] - ); - return false; - } - } - EventView::FlushStop(..) => { - if let Err(err) = proxysrc.task.flush_stop() { - gst_error!(SRC_CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStop failed {:?}", err] - ); - return false; - } - } - _ => (), - } - - 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 ProxySrcTask { - element: gst::Element, - src_pad: PadSrcWeak, - dataqueue: DataQueue, -} - -impl ProxySrcTask { - fn new(element: &gst::Element, src_pad: &PadSrc, dataqueue: DataQueue) -> Self { - ProxySrcTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - dataqueue, - } - } -} - -impl TaskImpl for ProxySrcTask { - fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(SRC_CAT, obj: &self.element, "Starting task"); - - let proxysrc = ProxySrc::from_instance(&self.element); - 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); - - if let Some(pending_queue) = shared_ctx.pending_queue.as_mut() { - pending_queue.notify_more_queue_space(); - } - - self.dataqueue.start(); - - gst_log!(SRC_CAT, obj: &self.element, "Task started"); - Ok(()) - } - .boxed() - } - - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = self.dataqueue.next().await; - - let item = match item { - Some(item) => item, - None => { - gst_log!(SRC_CAT, obj: &self.element, "DataQueue Stopped"); - return Err(gst::FlowError::Flushing); - } - }; - - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let proxysrc = ProxySrc::from_instance(&self.element); - let res = ProxySrcPadHandler::push_item(&pad, &proxysrc, item).await; - match res { - Ok(()) => { - gst_log!(SRC_CAT, obj: &self.element, "Successfully pushed item"); - let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); - let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); - shared_ctx.last_res = Ok(gst::FlowSuccess::Ok); - } - Err(gst::FlowError::Flushing) => { - gst_debug!(SRC_CAT, obj: &self.element, "Flushing"); - let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); - let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); - shared_ctx.last_res = Err(gst::FlowError::Flushing); - } - Err(gst::FlowError::Eos) => { - gst_debug!(SRC_CAT, obj: &self.element, "EOS"); - let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); - let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); - shared_ctx.last_res = Err(gst::FlowError::Eos); - } - Err(err) => { - gst_error!(SRC_CAT, obj: &self.element, "Got error {}", err); - gst_element_error!( - &self.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); - } - } - - res - } - .boxed() - } - - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(SRC_CAT, obj: &self.element, "Stopping task"); - - let proxysrc = ProxySrc::from_instance(&self.element); - let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); - let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); - - self.dataqueue.clear(); - self.dataqueue.stop(); - - shared_ctx.last_res = Err(gst::FlowError::Flushing); - - if let Some(mut pending_queue) = shared_ctx.pending_queue.take() { - pending_queue.notify_more_queue_space(); - } - - gst_log!(SRC_CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() - } - - fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(SRC_CAT, obj: &self.element, "Starting task flush"); - - let proxysrc = ProxySrc::from_instance(&self.element); - let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); - let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); - - self.dataqueue.clear(); - - shared_ctx.last_res = Err(gst::FlowError::Flushing); - - gst_log!(SRC_CAT, obj: &self.element, "Task flush started"); - Ok(()) - } - .boxed() - } -} - -#[derive(Debug)] -struct ProxySrc { - src_pad: PadSrc, - task: Task, - proxy_ctx: StdMutex>, - dataqueue: StdMutex>, - settings: StdMutex, -} - -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.clone()); - - self.task - .prepare(ProxySrcTask::new(element, &self.src_pad, dataqueue), 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) { - 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.dataqueue.lock().unwrap() = None; - *self.proxy_ctx.lock().unwrap() = None; - - gst_debug!(SRC_CAT, obj: element, "Unprepared"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(SRC_CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(SRC_CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(SRC_CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(SRC_CAT, obj: element, "Started"); - Ok(()) - } - - fn pause(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(SRC_CAT, obj: element, "Pausing"); - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing proxy source", - "Source/Generic", - "Thread-sharing proxy source", - "Sebastian Dröge ", - ); - - 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - Self { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - ProxySrcPadHandler, - ), - task: Task::default(), - proxy_ctx: StdMutex::new(None), - dataqueue: StdMutex::new(None), - settings: StdMutex::new(SettingsSrc::default()), - } - } -} - -impl ObjectImpl for ProxySrc { - 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 { - 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::().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_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); - } - _ => (), - } - - 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/threadshare/src/proxy/imp.rs b/generic/threadshare/src/proxy/imp.rs new file mode 100644 index 000000000..d4eb1aa2f --- /dev/null +++ b/generic/threadshare/src/proxy/imp.rs @@ -0,0 +1,1319 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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 crate::dataqueue::{DataQueue, DataQueueItem}; + +lazy_static! { + static ref PROXY_CONTEXTS: StdMutex>>> = + StdMutex::new(HashMap::new()); + static ref PROXY_SRC_PADS: StdMutex> = + StdMutex::new(HashMap::new()); + static ref PROXY_SINK_PADS: StdMutex> = + 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>, + scheduled: bool, + items: VecDeque, +} + +impl PendingQueue { + fn notify_more_queue_space(&mut self) { + self.more_queue_space_sender.take(); + } +} + +#[derive(Debug)] +struct ProxyContextInner { + name: String, + dataqueue: Option, + last_res: Result, + pending_queue: Option, + 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>, + 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 { + 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> { + let pad_weak = pad.downgrade(); + let element = element.clone().downcast::().unwrap(); + + 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> { + let pad_weak = pad.downgrade(); + let element = element.clone().downcast::().unwrap(); + 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.downcast_ref::().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().downcast::().unwrap(); + 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::Eos::builder().src(&element).build()); + } + EventView::FlushStop(..) => proxysink.start(&element), + _ => (), + } + + gst_log!(SINK_CAT, obj: pad.gst_pad(), "Queuing serialized {:?}", event); + proxysink + .enqueue_item(&element, DataQueueItem::Event(event)) + .await + .is_ok() + } + .boxed() + } +} + +#[derive(Debug)] +pub struct ProxySink { + sink_pad: PadSink, + proxy_ctx: StdMutex>, + settings: StdMutex, +} + +lazy_static! { + static ref SINK_CAT: gst::DebugCategory = gst::DebugCategory::new( + "ts-proxysink", + gst::DebugColorFlags::empty(), + Some("Thread-sharing proxy sink"), + ); +} + +impl ProxySink { + async fn schedule_pending_queue(&self, element: &super::ProxySink) { + loop { + let more_queue_space_receiver = { + let proxy_ctx = self.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: &super::ProxySink, + item: DataQueueItem, + ) -> Result { + 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 { + gst_log!(SINK_CAT, obj: element, "Scheduling pending queue now"); + pending_queue.scheduled = true; + + let wait_fut = self.schedule_pending_queue(element); + 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: &super::ProxySink) -> 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); + + gst_debug!(SINK_CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::ProxySink) { + gst_debug!(SINK_CAT, obj: element, "Unpreparing"); + *self.proxy_ctx.lock().unwrap() = None; + gst_debug!(SINK_CAT, obj: element, "Unprepared"); + } + + fn start(&self, element: &super::ProxySink) { + 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"); + } + + fn stop(&self, element: &super::ProxySink) { + 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"); + } +} + +impl ObjectSubclass for ProxySink { + const NAME: &'static str = "RsTsProxySink"; + type Type = super::ProxySink; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing proxy sink", + "Sink/Generic", + "Thread-sharing proxy sink", + "Sebastian Dröge ", + ); + + 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 with_class(klass: &Self::Class) -> Self { + Self { + sink_pad: PadSink::new( + gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), + ProxySinkPadHandler, + ), + proxy_ctx: StdMutex::new(None), + settings: StdMutex::new(SettingsSink::default()), + } + } +} + +impl ObjectImpl for ProxySink { + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.sink_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SINK); + } +} + +impl ElementImpl for ProxySink { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + gst::StateChange::ReadyToNull => { + self.unprepare(element); + } + _ => (), + } + + let success = self.parent_change_state(element, transition)?; + + if transition == gst::StateChange::ReadyToPaused { + self.start(element); + } + + 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(pending_queue) = shared_ctx.pending_queue.as_mut() { + 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(..) => { + if let Err(err) = proxysrc.task.flush_start() { + gst_error!(SRC_CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStart failed {:?}", err] + ); + return false; + } + } + EventView::FlushStop(..) => { + if let Err(err) = proxysrc.task.flush_stop() { + gst_error!(SRC_CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStop failed {:?}", err] + ); + return false; + } + } + _ => (), + } + + 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 ProxySrcTask { + element: super::ProxySrc, + src_pad: PadSrcWeak, + dataqueue: DataQueue, +} + +impl ProxySrcTask { + fn new(element: &super::ProxySrc, src_pad: &PadSrc, dataqueue: DataQueue) -> Self { + ProxySrcTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + dataqueue, + } + } +} + +impl TaskImpl for ProxySrcTask { + fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(SRC_CAT, obj: &self.element, "Starting task"); + + let proxysrc = ProxySrc::from_instance(&self.element); + 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); + + if let Some(pending_queue) = shared_ctx.pending_queue.as_mut() { + pending_queue.notify_more_queue_space(); + } + + self.dataqueue.start(); + + gst_log!(SRC_CAT, obj: &self.element, "Task started"); + Ok(()) + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = self.dataqueue.next().await; + + let item = match item { + Some(item) => item, + None => { + gst_log!(SRC_CAT, obj: &self.element, "DataQueue Stopped"); + return Err(gst::FlowError::Flushing); + } + }; + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let proxysrc = ProxySrc::from_instance(&self.element); + let res = ProxySrcPadHandler::push_item(&pad, &proxysrc, item).await; + match res { + Ok(()) => { + gst_log!(SRC_CAT, obj: &self.element, "Successfully pushed item"); + let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); + let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); + shared_ctx.last_res = Ok(gst::FlowSuccess::Ok); + } + Err(gst::FlowError::Flushing) => { + gst_debug!(SRC_CAT, obj: &self.element, "Flushing"); + let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); + let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); + shared_ctx.last_res = Err(gst::FlowError::Flushing); + } + Err(gst::FlowError::Eos) => { + gst_debug!(SRC_CAT, obj: &self.element, "EOS"); + let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); + let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); + shared_ctx.last_res = Err(gst::FlowError::Eos); + } + Err(err) => { + gst_error!(SRC_CAT, obj: &self.element, "Got error {}", err); + gst_element_error!( + &self.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); + } + } + + res + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(SRC_CAT, obj: &self.element, "Stopping task"); + + let proxysrc = ProxySrc::from_instance(&self.element); + let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); + let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); + + self.dataqueue.clear(); + self.dataqueue.stop(); + + shared_ctx.last_res = Err(gst::FlowError::Flushing); + + if let Some(mut pending_queue) = shared_ctx.pending_queue.take() { + pending_queue.notify_more_queue_space(); + } + + gst_log!(SRC_CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } + + fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(SRC_CAT, obj: &self.element, "Starting task flush"); + + let proxysrc = ProxySrc::from_instance(&self.element); + let proxy_ctx = proxysrc.proxy_ctx.lock().unwrap(); + let mut shared_ctx = proxy_ctx.as_ref().unwrap().lock_shared(); + + self.dataqueue.clear(); + + shared_ctx.last_res = Err(gst::FlowError::Flushing); + + gst_log!(SRC_CAT, obj: &self.element, "Task flush started"); + Ok(()) + } + .boxed() + } +} + +#[derive(Debug)] +pub struct ProxySrc { + src_pad: PadSrc, + task: Task, + proxy_ctx: StdMutex>, + dataqueue: StdMutex>, + settings: StdMutex, +} + +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: &super::ProxySrc) -> 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.clone()); + + self.task + .prepare(ProxySrcTask::new(element, &self.src_pad, dataqueue), 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: &super::ProxySrc) { + 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.dataqueue.lock().unwrap() = None; + *self.proxy_ctx.lock().unwrap() = None; + + gst_debug!(SRC_CAT, obj: element, "Unprepared"); + } + + fn stop(&self, element: &super::ProxySrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(SRC_CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(SRC_CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::ProxySrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(SRC_CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(SRC_CAT, obj: element, "Started"); + Ok(()) + } + + fn pause(&self, element: &super::ProxySrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(SRC_CAT, obj: element, "Pausing"); + self.task.pause()?; + gst_debug!(SRC_CAT, obj: element, "Paused"); + Ok(()) + } +} + +impl ObjectSubclass for ProxySrc { + const NAME: &'static str = "RsTsProxySrc"; + type Type = super::ProxySrc; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing proxy source", + "Source/Generic", + "Thread-sharing proxy source", + "Sebastian Dröge ", + ); + + 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 with_class(klass: &Self::Class) -> Self { + Self { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + ProxySrcPadHandler, + ), + task: Task::default(), + proxy_ctx: StdMutex::new(None), + dataqueue: StdMutex::new(None), + settings: StdMutex::new(SettingsSrc::default()), + } + } +} + +impl ObjectImpl for ProxySrc { + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SOURCE); + } +} + +impl ElementImpl for ProxySrc { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + 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) + } +} diff --git a/generic/threadshare/src/proxy/mod.rs b/generic/threadshare/src/proxy/mod.rs new file mode 100644 index 000000000..2bf3b2dd3 --- /dev/null +++ b/generic/threadshare/src/proxy/mod.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct ProxySink(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for ProxySink {} +unsafe impl Sync for ProxySink {} + +glib_wrapper! { + pub struct ProxySrc(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for ProxySrc {} +unsafe impl Sync for ProxySrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-proxysink", + gst::Rank::None, + ProxySink::static_type(), + )?; + gst::Element::register( + Some(plugin), + "ts-proxysrc", + gst::Rank::None, + ProxySrc::static_type(), + ) +} diff --git a/generic/threadshare/src/queue.rs b/generic/threadshare/src/queue.rs deleted file mode 100644 index e9fec3c71..000000000 --- a/generic/threadshare/src/queue.rs +++ /dev/null @@ -1,897 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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, PadSrcWeak, Task}; - -use super::dataqueue::{DataQueue, DataQueueItem}; - -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>, - scheduled: bool, - items: VecDeque, -} - -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> { - 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> { - 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() { - if let Err(err) = queue.task.flush_start() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStart failed {:?}", err] - ); - return false; - } - } - - 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() { - if let Err(err) = queue.task.flush_stop() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStop failed {:?}", err] - ); - return false; - } - } - - 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<'_>, - queue: &Queue, - item: DataQueueItem, - ) -> Result<(), gst::FlowError> { - 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(..) => { - if let Err(err) = queue.task.flush_start() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); - } - } - EventView::FlushStop(..) => { - if let Err(err) = queue.task.flush_stop() { - gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); - gst_element_error!( - element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["FlushStop failed {:?}", err] - ); - return false; - } - } - _ => (), - } - - 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::Scheduling::new(); - 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::>(), - ); - 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 QueueTask { - element: gst::Element, - src_pad: PadSrcWeak, - dataqueue: DataQueue, -} - -impl QueueTask { - fn new(element: &gst::Element, src_pad: &PadSrc, dataqueue: DataQueue) -> Self { - QueueTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - dataqueue, - } - } -} - -impl TaskImpl for QueueTask { - fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task"); - - let queue = Queue::from_instance(&self.element); - let mut last_res = queue.last_res.lock().unwrap(); - - self.dataqueue.start(); - - *last_res = Ok(gst::FlowSuccess::Ok); - - gst_log!(CAT, obj: &self.element, "Task started"); - Ok(()) - } - .boxed() - } - - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = self.dataqueue.next().await; - - let item = match item { - Some(item) => item, - None => { - gst_log!(CAT, obj: &self.element, "DataQueue Stopped"); - return Err(gst::FlowError::Flushing); - } - }; - - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let queue = Queue::from_instance(&self.element); - let res = QueuePadSrcHandler::push_item(&pad, &queue, item).await; - match res { - Ok(()) => { - gst_log!(CAT, obj: &self.element, "Successfully pushed item"); - *queue.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok); - } - Err(gst::FlowError::Flushing) => { - gst_debug!(CAT, obj: &self.element, "Flushing"); - *queue.last_res.lock().unwrap() = Err(gst::FlowError::Flushing); - } - Err(gst::FlowError::Eos) => { - gst_debug!(CAT, obj: &self.element, "EOS"); - *queue.last_res.lock().unwrap() = Err(gst::FlowError::Eos); - pad.push_event(gst::event::Eos::new()).await; - } - Err(err) => { - gst_error!(CAT, obj: &self.element, "Got error {}", err); - gst_element_error!( - &self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - *queue.last_res.lock().unwrap() = Err(err); - } - } - - res - } - .boxed() - } - - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task"); - - let queue = Queue::from_instance(&self.element); - let mut last_res = queue.last_res.lock().unwrap(); - - self.dataqueue.stop(); - self.dataqueue.clear(); - - if let Some(mut pending_queue) = queue.pending_queue.lock().unwrap().take() { - pending_queue.notify_more_queue_space(); - } - - *last_res = Err(gst::FlowError::Flushing); - - gst_log!(CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() - } - - fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task flush"); - - let queue = Queue::from_instance(&self.element); - let mut last_res = queue.last_res.lock().unwrap(); - - self.dataqueue.clear(); - - if let Some(mut pending_queue) = queue.pending_queue.lock().unwrap().take() { - pending_queue.notify_more_queue_space(); - } - - *last_res = Err(gst::FlowError::Flushing); - - gst_log!(CAT, obj: &self.element, "Task flush started"); - Ok(()) - } - .boxed() - } -} - -#[derive(Debug)] -struct Queue { - sink_pad: PadSink, - src_pad: PadSrc, - task: Task, - dataqueue: StdMutex>, - pending_queue: StdMutex>, - last_res: StdMutex>, - settings: StdMutex, -} - -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, - 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 */ - async fn schedule_pending_queue(&self, element: &gst::Element) { - loop { - let more_queue_space_receiver = { - let dataqueue = self.dataqueue.lock().unwrap(); - if dataqueue.is_none() { - return; - } - let mut pending_queue_grd = self.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 { - 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 { - gst_log!(CAT, obj: element, "Scheduling pending queue now"); - pending_queue.as_mut().unwrap().scheduled = true; - - let wait_fut = self.schedule_pending_queue(element); - 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.clone()); - - 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(QueueTask::new(element, &self.src_pad, dataqueue), context) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_debug!(CAT, obj: element, "Prepared"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - - self.task.unprepare().unwrap(); - - *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"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) - } -} - -impl ObjectSubclass for Queue { - const NAME: &'static str = "RsTsQueue"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing queue", - "Generic", - "Simple data queue", - "Sebastian Dröge ", - ); - - 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - Self { - sink_pad: PadSink::new( - gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), - QueuePadSinkHandler, - ), - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - QueuePadSrcHandler, - ), - 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 { - 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 { - 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::().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_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); - } - _ => (), - } - - 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/threadshare/src/queue/imp.rs b/generic/threadshare/src/queue/imp.rs new file mode 100644 index 000000000..28ac0a351 --- /dev/null +++ b/generic/threadshare/src/queue/imp.rs @@ -0,0 +1,893 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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, PadSrcWeak, Task}; + +use crate::dataqueue::{DataQueue, DataQueueItem}; + +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>, + scheduled: bool, + items: VecDeque, +} + +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> { + let pad_weak = pad.downgrade(); + let element = element.clone().downcast::().unwrap(); + 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> { + let pad_weak = pad.downgrade(); + let element = element.clone().downcast::().unwrap(); + 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() { + if let Err(err) = queue.task.flush_start() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStart failed {:?}", err] + ); + return false; + } + } + + 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().downcast::().unwrap(); + async move { + let pad = pad_weak.upgrade().expect("PadSink no longer exists"); + let queue = Queue::from_instance(&element); + + if let EventView::FlushStop(..) = event.view() { + if let Err(err) = queue.task.flush_stop() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStop failed {:?}", err] + ); + return false; + } + } + + 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<'_>, + queue: &Queue, + item: DataQueueItem, + ) -> Result<(), gst::FlowError> { + 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(..) => { + if let Err(err) = queue.task.flush_start() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStart failed {:?}", err); + } + } + EventView::FlushStop(..) => { + if let Err(err) = queue.task.flush_stop() { + gst_error!(CAT, obj: pad.gst_pad(), "FlushStop failed {:?}", err); + gst_element_error!( + element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["FlushStop failed {:?}", err] + ); + return false; + } + } + _ => (), + } + + 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::Scheduling::new(); + 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::>(), + ); + 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 QueueTask { + element: super::Queue, + src_pad: PadSrcWeak, + dataqueue: DataQueue, +} + +impl QueueTask { + fn new(element: &super::Queue, src_pad: &PadSrc, dataqueue: DataQueue) -> Self { + QueueTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + dataqueue, + } + } +} + +impl TaskImpl for QueueTask { + fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task"); + + let queue = Queue::from_instance(&self.element); + let mut last_res = queue.last_res.lock().unwrap(); + + self.dataqueue.start(); + + *last_res = Ok(gst::FlowSuccess::Ok); + + gst_log!(CAT, obj: &self.element, "Task started"); + Ok(()) + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = self.dataqueue.next().await; + + let item = match item { + Some(item) => item, + None => { + gst_log!(CAT, obj: &self.element, "DataQueue Stopped"); + return Err(gst::FlowError::Flushing); + } + }; + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let queue = Queue::from_instance(&self.element); + let res = QueuePadSrcHandler::push_item(&pad, &queue, item).await; + match res { + Ok(()) => { + gst_log!(CAT, obj: &self.element, "Successfully pushed item"); + *queue.last_res.lock().unwrap() = Ok(gst::FlowSuccess::Ok); + } + Err(gst::FlowError::Flushing) => { + gst_debug!(CAT, obj: &self.element, "Flushing"); + *queue.last_res.lock().unwrap() = Err(gst::FlowError::Flushing); + } + Err(gst::FlowError::Eos) => { + gst_debug!(CAT, obj: &self.element, "EOS"); + *queue.last_res.lock().unwrap() = Err(gst::FlowError::Eos); + pad.push_event(gst::event::Eos::new()).await; + } + Err(err) => { + gst_error!(CAT, obj: &self.element, "Got error {}", err); + gst_element_error!( + &self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + *queue.last_res.lock().unwrap() = Err(err); + } + } + + res + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task"); + + let queue = Queue::from_instance(&self.element); + let mut last_res = queue.last_res.lock().unwrap(); + + self.dataqueue.stop(); + self.dataqueue.clear(); + + if let Some(mut pending_queue) = queue.pending_queue.lock().unwrap().take() { + pending_queue.notify_more_queue_space(); + } + + *last_res = Err(gst::FlowError::Flushing); + + gst_log!(CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } + + fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task flush"); + + let queue = Queue::from_instance(&self.element); + let mut last_res = queue.last_res.lock().unwrap(); + + self.dataqueue.clear(); + + if let Some(mut pending_queue) = queue.pending_queue.lock().unwrap().take() { + pending_queue.notify_more_queue_space(); + } + + *last_res = Err(gst::FlowError::Flushing); + + gst_log!(CAT, obj: &self.element, "Task flush started"); + Ok(()) + } + .boxed() + } +} + +#[derive(Debug)] +pub struct Queue { + sink_pad: PadSink, + src_pad: PadSrc, + task: Task, + dataqueue: StdMutex>, + pending_queue: StdMutex>, + last_res: StdMutex>, + settings: StdMutex, +} + +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, + 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 */ + async fn schedule_pending_queue(&self, element: &super::Queue) { + loop { + let more_queue_space_receiver = { + let dataqueue = self.dataqueue.lock().unwrap(); + if dataqueue.is_none() { + return; + } + let mut pending_queue_grd = self.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: &super::Queue, + item: DataQueueItem, + ) -> Result { + 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 { + gst_log!(CAT, obj: element, "Scheduling pending queue now"); + pending_queue.as_mut().unwrap().scheduled = true; + + let wait_fut = self.schedule_pending_queue(element); + 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: &super::Queue) -> 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.clone()); + + 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(QueueTask::new(element, &self.src_pad, dataqueue), context) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_debug!(CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::Queue) { + gst_debug!(CAT, obj: element, "Unpreparing"); + + self.task.unprepare().unwrap(); + + *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"); + } + + fn stop(&self, element: &super::Queue) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::Queue) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } +} + +impl ObjectSubclass for Queue { + const NAME: &'static str = "RsTsQueue"; + type Type = super::Queue; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing queue", + "Generic", + "Simple data queue", + "Sebastian Dröge ", + ); + + 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 with_class(klass: &Self::Class) -> Self { + Self { + sink_pad: PadSink::new( + gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), + QueuePadSinkHandler, + ), + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + QueuePadSrcHandler, + ), + 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 { + fn set_property(&self, _obj: &Self::Type, 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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.sink_pad.gst_pad()).unwrap(); + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + } +} + +impl ElementImpl for Queue { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + let success = self.parent_change_state(element, transition)?; + + if transition == gst::StateChange::ReadyToPaused { + self.start(element).map_err(|_| gst::StateChangeError)?; + } + + Ok(success) + } +} diff --git a/generic/threadshare/src/queue/mod.rs b/generic/threadshare/src/queue/mod.rs new file mode 100644 index 000000000..153939f9d --- /dev/null +++ b/generic/threadshare/src/queue/mod.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct Queue(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for Queue {} +unsafe impl Sync for Queue {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-queue", + gst::Rank::None, + Queue::static_type(), + ) +} diff --git a/generic/threadshare/src/runtime/pad.rs b/generic/threadshare/src/runtime/pad.rs index 89e5e01d4..12da4d3b4 100644 --- a/generic/threadshare/src/runtime/pad.rs +++ b/generic/threadshare/src/runtime/pad.rs @@ -402,7 +402,11 @@ impl PadSrc { }, move |imp, element| { let this_ref = PadSrcRef::new(inner_arc); - handler.src_activate(&this_ref, imp, element) + handler.src_activate( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + ) }, ) }); @@ -425,7 +429,13 @@ impl PadSrc { move |imp, element| { let this_ref = PadSrcRef::new(inner_arc); this_ref.activate_mode_hook(mode, active)?; - handler.src_activatemode(&this_ref, imp, element, mode, active) + handler.src_activatemode( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + mode, + active, + ) }, ) }); @@ -443,7 +453,12 @@ impl PadSrc { || Err(FlowError::Error), move |imp, element| { let this_ref = PadSrcRef::new(inner_arc); - handler.src_event_full(&this_ref, imp, &element, event) + handler.src_event_full( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + event, + ) }, ) }); @@ -459,7 +474,7 @@ impl PadSrc { move |imp, element| { let this_ref = PadSrcRef::new(inner_arc); if !query.is_serialized() { - handler.src_query(&this_ref, imp, &element, query) + handler.src_query(&this_ref, imp, element.dynamic_cast_ref::().unwrap(), query) } else { gst_fixme!(RUNTIME_CAT, obj: this_ref.gst_pad(), "Serialized Query not supported"); false @@ -509,6 +524,8 @@ impl Deref for PadSrc { /// [`pad` module]: index.html pub trait PadSinkHandler: Clone + Send + Sync + 'static { type ElementImpl: ElementImpl + ObjectSubclass; + // FIXME: Once associated type bounds are stable we should use ObjectSubclass::Type below + // instead of &gst::Element fn sink_activate( &self, @@ -806,7 +823,11 @@ impl PadSink { }, move |imp, element| { let this_ref = PadSinkRef::new(inner_arc); - handler.sink_activate(&this_ref, imp, element) + handler.sink_activate( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + ) }, ) }); @@ -830,7 +851,13 @@ impl PadSink { let this_ref = PadSinkRef::new(inner_arc); this_ref.activate_mode_hook(mode, active)?; - handler.sink_activatemode(&this_ref, imp, element, mode, active) + handler.sink_activatemode( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + mode, + active, + ) }, ) }); @@ -848,10 +875,12 @@ impl PadSink { if Context::current_has_sub_tasks() { let this_weak = PadSinkWeak(Arc::downgrade(&inner_arc)); let handler = handler.clone(); - let element = element.clone(); + let element = + element.clone().dynamic_cast::().unwrap(); let delayed_fut = async move { - let imp = - ::from_instance(&element); + let imp = ::from_instance( + element.unsafe_cast_ref(), + ); let this_ref = this_weak.upgrade().ok_or(gst::FlowError::Flushing)?; handler.sink_chain(&this_ref, imp, &element, buffer).await @@ -861,8 +890,12 @@ impl PadSink { Ok(gst::FlowSuccess::Ok) } else { let this_ref = PadSinkRef::new(inner_arc); - let chain_fut = - handler.sink_chain(&this_ref, imp, &element, buffer); + let chain_fut = handler.sink_chain( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + buffer, + ); this_ref.handle_future(chain_fut) } }, @@ -882,10 +915,12 @@ impl PadSink { if Context::current_has_sub_tasks() { let this_weak = PadSinkWeak(Arc::downgrade(&inner_arc)); let handler = handler.clone(); - let element = element.clone(); + let element = + element.clone().dynamic_cast::().unwrap(); let delayed_fut = async move { - let imp = - ::from_instance(&element); + let imp = ::from_instance( + element.unsafe_cast_ref(), + ); let this_ref = this_weak.upgrade().ok_or(gst::FlowError::Flushing)?; handler @@ -897,8 +932,12 @@ impl PadSink { Ok(gst::FlowSuccess::Ok) } else { let this_ref = PadSinkRef::new(inner_arc); - let chain_list_fut = - handler.sink_chain_list(&this_ref, imp, &element, list); + let chain_list_fut = handler.sink_chain_list( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + list, + ); this_ref.handle_future(chain_list_fut) } }, @@ -921,10 +960,11 @@ impl PadSink { if Context::current_has_sub_tasks() { let this_weak = PadSinkWeak(Arc::downgrade(&inner_arc)); let handler = handler.clone(); - let element = element.clone(); + let element = + element.clone().dynamic_cast::().unwrap(); let delayed_fut = async move { let imp = ::from_instance( - &element, + element.unsafe_cast_ref(), ); let this_ref = this_weak.upgrade().ok_or(gst::FlowError::Flushing)?; @@ -942,13 +982,21 @@ impl PadSink { } else { let this_ref = PadSinkRef::new(inner_arc); let event_fut = handler.sink_event_full_serialized( - &this_ref, imp, &element, event, + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + event, ); this_ref.handle_future(event_fut) } } else { let this_ref = PadSinkRef::new(inner_arc); - handler.sink_event_full(&this_ref, imp, &element, event) + handler.sink_event_full( + &this_ref, + imp, + element.dynamic_cast_ref::().unwrap(), + event, + ) } }, ) @@ -965,7 +1013,7 @@ impl PadSink { move |imp, element| { let this_ref = PadSinkRef::new(inner_arc); if !query.is_serialized() { - handler.sink_query(&this_ref, imp, &element, query) + handler.sink_query(&this_ref, imp, element.dynamic_cast_ref::().unwrap(), query) } else { gst_fixme!(RUNTIME_CAT, obj: this_ref.gst_pad(), "Serialized Query not supported"); false diff --git a/generic/threadshare/src/tcpclientsrc.rs b/generic/threadshare/src/tcpclientsrc.rs deleted file mode 100644 index b5e8e967e..000000000 --- a/generic/threadshare/src/tcpclientsrc.rs +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// Copyright (C) 2018 LEE Dongjun -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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::io; -use std::net::{IpAddr, SocketAddr}; -use std::sync::Arc; -use std::sync::Mutex as StdMutex; -use std::u16; -use std::u32; - -use tokio::io::AsyncReadExt; - -use crate::runtime::prelude::*; -use crate::runtime::task; -use crate::runtime::{Context, PadSrc, PadSrcRef, PadSrcWeak, Task, TaskState}; - -use super::socket::{Socket, SocketError, SocketRead}; - -const DEFAULT_HOST: Option<&str> = Some("127.0.0.1"); -const DEFAULT_PORT: i32 = 4953; -const DEFAULT_CAPS: Option = None; -const DEFAULT_BLOCKSIZE: u32 = 4096; -const DEFAULT_CONTEXT: &str = ""; -const DEFAULT_CONTEXT_WAIT: u32 = 0; - -#[derive(Debug, Clone)] -struct Settings { - host: Option, - port: i32, - caps: Option, - blocksize: u32, - context: String, - context_wait: u32, -} - -impl Default for Settings { - fn default() -> Self { - Settings { - host: DEFAULT_HOST.map(Into::into), - port: DEFAULT_PORT, - caps: DEFAULT_CAPS, - blocksize: DEFAULT_BLOCKSIZE, - context: DEFAULT_CONTEXT.into(), - context_wait: DEFAULT_CONTEXT_WAIT, - } - } -} - -static PROPERTIES: [subclass::Property; 6] = [ - subclass::Property("host", |name| { - glib::ParamSpec::string( - name, - "Host", - "The host IP address to receive packets from", - DEFAULT_HOST, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("port", |name| { - glib::ParamSpec::int( - name, - "Port", - "Port to receive packets from", - 0, - u16::MAX as i32, - 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("blocksize", |name| { - glib::ParamSpec::uint( - name, - "Blocksize", - "Size in bytes to read per buffer (-1 = default)", - 0, - u32::MAX, - DEFAULT_BLOCKSIZE, - 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, - ) - }), -]; - -pub struct TcpClientReader(tokio::net::TcpStream); - -impl TcpClientReader { - pub fn new(socket: tokio::net::TcpStream) -> Self { - TcpClientReader(socket) - } -} - -impl SocketRead for TcpClientReader { - const DO_TIMESTAMP: bool = false; - - fn read<'buf>( - &'buf mut self, - buffer: &'buf mut [u8], - ) -> BoxFuture<'buf, io::Result<(usize, Option)>> { - async move { self.0.read(buffer).await.map(|read_size| (read_size, None)) }.boxed() - } -} - -#[derive(Debug)] -struct TcpClientSrcPadHandlerState { - need_initial_events: bool, - need_segment: bool, - caps: Option, -} - -impl Default for TcpClientSrcPadHandlerState { - fn default() -> Self { - TcpClientSrcPadHandlerState { - need_initial_events: true, - need_segment: true, - caps: None, - } - } -} - -#[derive(Debug, Default)] -struct TcpClientSrcPadHandlerInner { - state: FutMutex, - configured_caps: StdMutex>, -} - -#[derive(Clone, Debug, Default)] -struct TcpClientSrcPadHandler(Arc); - -impl TcpClientSrcPadHandler { - fn prepare(&self, caps: Option) { - self.0 - .state - .try_lock() - .expect("State locked elsewhere") - .caps = caps; - } - - async fn reset_state(&self) { - *self.0.configured_caps.lock().unwrap() = None; - } - - async fn set_need_segment(&self) { - self.0.state.lock().await.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::(), rand::random::()); - let stream_start_evt = gst::event::StreamStart::builder(&stream_id) - .group_id(gst::GroupId::next()) - .build(); - pad.push_event(stream_start_evt).await; - - if let Some(ref caps) = state.caps { - pad.push_event(gst::event::Caps::new(&caps)).await; - *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); - } - - state.need_initial_events = false; - } - - if state.need_segment { - let segment_evt = - gst::event::Segment::new(&gst::FormattedSegment::::new()); - 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_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer); - - self.push_prelude(pad, element).await; - - if buffer.get_size() == 0 { - pad.push_event(gst::event::Eos::new()).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.task.flush_start().is_ok(), - EventView::FlushStop(..) => tcpclientsrc.task.flush_stop().is_ok(), - 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 TcpClientSrcTask { - element: gst::Element, - src_pad: PadSrcWeak, - src_pad_handler: TcpClientSrcPadHandler, - saddr: SocketAddr, - buffer_pool: Option, - socket: Option>, -} - -impl TcpClientSrcTask { - fn new( - element: &gst::Element, - src_pad: &PadSrc, - src_pad_handler: &TcpClientSrcPadHandler, - saddr: SocketAddr, - buffer_pool: gst::BufferPool, - ) -> Self { - TcpClientSrcTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - src_pad_handler: src_pad_handler.clone(), - saddr, - buffer_pool: Some(buffer_pool), - socket: None, - } - } -} - -impl TaskImpl for TcpClientSrcTask { - fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Preparing task connecting to {:?}", self.saddr); - - let socket = tokio::net::TcpStream::connect(self.saddr) - .await - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to connect to {:?}: {:?}", self.saddr, err] - ) - })?; - - self.socket = Some( - Socket::try_new( - self.element.clone(), - self.buffer_pool.take().unwrap(), - TcpClientReader::new(socket), - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to prepare socket {:?}", err] - ) - })?, - ); - - gst_log!(CAT, obj: &self.element, "Task prepared"); - Ok(()) - } - .boxed() - } - - fn handle_action_error( - &mut self, - trigger: task::Trigger, - state: TaskState, - err: gst::ErrorMessage, - ) -> BoxFuture<'_, task::Trigger> { - async move { - match trigger { - task::Trigger::Prepare => { - gst_error!(CAT, "Task preparation failed: {:?}", err); - self.element.post_error_message(err); - - task::Trigger::Error - } - other => unreachable!("Action error for {:?} in state {:?}", other, state), - } - } - .boxed() - } - - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = self.socket.as_mut().unwrap().next().await; - - let buffer = match item { - Some(Ok((buffer, _))) => buffer, - Some(Err(err)) => { - gst_error!(CAT, obj: &self.element, "Got error {:?}", err); - match err { - SocketError::Gst(err) => { - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - } - SocketError::Io(err) => { - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("I/O error"), - ["streaming stopped, I/O error {}", err] - ); - } - } - return Err(gst::FlowError::Error); - } - None => { - gst_log!(CAT, obj: &self.element, "SocketStream Stopped"); - return Err(gst::FlowError::Flushing); - } - }; - - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let res = self - .src_pad_handler - .push_buffer(&pad, &self.element, buffer) - .await; - match res { - Ok(_) => { - gst_log!(CAT, obj: &self.element, "Successfully pushed buffer"); - } - Err(gst::FlowError::Flushing) => { - gst_debug!(CAT, obj: &self.element, "Flushing"); - } - Err(gst::FlowError::Eos) => { - gst_debug!(CAT, obj: &self.element, "EOS"); - pad.push_event(gst::event::Eos::new()).await; - } - Err(err) => { - gst_error!(CAT, obj: &self.element, "Got error {}", err); - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - } - } - - res.map(drop) - } - .boxed() - } - - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task"); - self.src_pad_handler.reset_state().await; - gst_log!(CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() - } - - fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task flush"); - self.src_pad_handler.set_need_segment().await; - gst_log!(CAT, obj: &self.element, "Task flush stopped"); - Ok(()) - } - .boxed() - } -} - -struct TcpClientSrc { - src_pad: PadSrc, - src_pad_handler: TcpClientSrcPadHandler, - task: Task, - settings: StdMutex, -} - -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 host: IpAddr = match settings.host { - None => { - return Err(gst_error_msg!( - gst::ResourceError::Settings, - ["No host set"] - )); - } - Some(ref host) => match host.parse() { - Err(err) => { - return Err(gst_error_msg!( - gst::ResourceError::Settings, - ["Invalid host '{}' set: {}", host, err] - )); - } - Ok(host) => host, - }, - }; - let port = settings.port; - - let buffer_pool = gst::BufferPool::new(); - let mut config = buffer_pool.get_config(); - config.set_params(None, settings.blocksize, 0, 0); - buffer_pool.set_config(config).map_err(|_| { - gst_error_msg!( - gst::ResourceError::Settings, - ["Failed to configure buffer pool"] - ) - })?; - - let saddr = SocketAddr::new(host, port as u16); - - self.src_pad_handler.prepare(settings.caps); - - self.task - .prepare( - TcpClientSrcTask::new( - element, - &self.src_pad, - &self.src_pad_handler, - saddr, - buffer_pool, - ), - context, - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_debug!(CAT, obj: element, "Prepared"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - self.task.unprepare().unwrap(); - gst_debug!(CAT, obj: element, "Unprepared"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) - } - - fn pause(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Pausing"); - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing TCP client source", - "Source/Network", - "Receives data over the network via TCP", - "Sebastian Dröge , LEE Dongjun ", - ); - - 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 with_class(klass: &subclass::simple::ClassStruct) -> Self { - let src_pad_handler = TcpClientSrcPadHandler::default(); - - Self { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - src_pad_handler.clone(), - ), - src_pad_handler, - task: Task::default(), - settings: StdMutex::new(Settings::default()), - } - } -} - -impl ObjectImpl for TcpClientSrc { - 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("host", ..) => { - settings.host = 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("blocksize", ..) => { - settings.blocksize = 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 { - 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("caps", ..) => Ok(settings.caps.to_value()), - subclass::Property("blocksize", ..) => Ok(settings.blocksize.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::().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_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); - } - _ => (), - } - - 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/threadshare/src/tcpclientsrc/imp.rs b/generic/threadshare/src/tcpclientsrc/imp.rs new file mode 100644 index 000000000..809f47d1b --- /dev/null +++ b/generic/threadshare/src/tcpclientsrc/imp.rs @@ -0,0 +1,756 @@ +// Copyright (C) 2018 Sebastian Dröge +// Copyright (C) 2018 LEE Dongjun +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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::io; +use std::net::{IpAddr, SocketAddr}; +use std::sync::Arc; +use std::sync::Mutex as StdMutex; +use std::u16; +use std::u32; + +use tokio::io::AsyncReadExt; + +use crate::runtime::prelude::*; +use crate::runtime::task; +use crate::runtime::{Context, PadSrc, PadSrcRef, PadSrcWeak, Task, TaskState}; + +use crate::socket::{Socket, SocketError, SocketRead}; + +const DEFAULT_HOST: Option<&str> = Some("127.0.0.1"); +const DEFAULT_PORT: i32 = 4953; +const DEFAULT_CAPS: Option = None; +const DEFAULT_BLOCKSIZE: u32 = 4096; +const DEFAULT_CONTEXT: &str = ""; +const DEFAULT_CONTEXT_WAIT: u32 = 0; + +#[derive(Debug, Clone)] +struct Settings { + host: Option, + port: i32, + caps: Option, + blocksize: u32, + context: String, + context_wait: u32, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + host: DEFAULT_HOST.map(Into::into), + port: DEFAULT_PORT, + caps: DEFAULT_CAPS, + blocksize: DEFAULT_BLOCKSIZE, + context: DEFAULT_CONTEXT.into(), + context_wait: DEFAULT_CONTEXT_WAIT, + } + } +} + +static PROPERTIES: [subclass::Property; 6] = [ + subclass::Property("host", |name| { + glib::ParamSpec::string( + name, + "Host", + "The host IP address to receive packets from", + DEFAULT_HOST, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("port", |name| { + glib::ParamSpec::int( + name, + "Port", + "Port to receive packets from", + 0, + u16::MAX as i32, + 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("blocksize", |name| { + glib::ParamSpec::uint( + name, + "Blocksize", + "Size in bytes to read per buffer (-1 = default)", + 0, + u32::MAX, + DEFAULT_BLOCKSIZE, + 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 TcpClientReader(tokio::net::TcpStream); + +impl TcpClientReader { + pub fn new(socket: tokio::net::TcpStream) -> Self { + TcpClientReader(socket) + } +} + +impl SocketRead for TcpClientReader { + const DO_TIMESTAMP: bool = false; + + fn read<'buf>( + &'buf mut self, + buffer: &'buf mut [u8], + ) -> BoxFuture<'buf, io::Result<(usize, Option)>> { + async move { self.0.read(buffer).await.map(|read_size| (read_size, None)) }.boxed() + } +} + +#[derive(Debug)] +struct TcpClientSrcPadHandlerState { + need_initial_events: bool, + need_segment: bool, + caps: Option, +} + +impl Default for TcpClientSrcPadHandlerState { + fn default() -> Self { + TcpClientSrcPadHandlerState { + need_initial_events: true, + need_segment: true, + caps: None, + } + } +} + +#[derive(Debug, Default)] +struct TcpClientSrcPadHandlerInner { + state: FutMutex, + configured_caps: StdMutex>, +} + +#[derive(Clone, Debug, Default)] +struct TcpClientSrcPadHandler(Arc); + +impl TcpClientSrcPadHandler { + fn prepare(&self, caps: Option) { + self.0 + .state + .try_lock() + .expect("State locked elsewhere") + .caps = caps; + } + + async fn reset_state(&self) { + *self.0.configured_caps.lock().unwrap() = None; + } + + async fn set_need_segment(&self) { + self.0.state.lock().await.need_segment = true; + } + + async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &super::TcpClientSrc) { + 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::(), rand::random::()); + let stream_start_evt = gst::event::StreamStart::builder(&stream_id) + .group_id(gst::GroupId::next()) + .build(); + pad.push_event(stream_start_evt).await; + + if let Some(ref caps) = state.caps { + pad.push_event(gst::event::Caps::new(&caps)).await; + *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); + } + + state.need_initial_events = false; + } + + if state.need_segment { + let segment_evt = + gst::event::Segment::new(&gst::FormattedSegment::::new()); + pad.push_event(segment_evt).await; + + state.need_segment = false; + } + } + + async fn push_buffer( + &self, + pad: &PadSrcRef<'_>, + element: &super::TcpClientSrc, + buffer: gst::Buffer, + ) -> Result { + gst_log!(CAT, obj: pad.gst_pad(), "Handling {:?}", buffer); + + self.push_prelude(pad, element).await; + + if buffer.get_size() == 0 { + pad.push_event(gst::event::Eos::new()).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.task.flush_start().is_ok(), + EventView::FlushStop(..) => tcpclientsrc.task.flush_stop().is_ok(), + 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 TcpClientSrcTask { + element: super::TcpClientSrc, + src_pad: PadSrcWeak, + src_pad_handler: TcpClientSrcPadHandler, + saddr: SocketAddr, + buffer_pool: Option, + socket: Option>, +} + +impl TcpClientSrcTask { + fn new( + element: &super::TcpClientSrc, + src_pad: &PadSrc, + src_pad_handler: &TcpClientSrcPadHandler, + saddr: SocketAddr, + buffer_pool: gst::BufferPool, + ) -> Self { + TcpClientSrcTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + src_pad_handler: src_pad_handler.clone(), + saddr, + buffer_pool: Some(buffer_pool), + socket: None, + } + } +} + +impl TaskImpl for TcpClientSrcTask { + fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Preparing task connecting to {:?}", self.saddr); + + let socket = tokio::net::TcpStream::connect(self.saddr) + .await + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to connect to {:?}: {:?}", self.saddr, err] + ) + })?; + + self.socket = Some( + Socket::try_new( + self.element.clone().upcast(), + self.buffer_pool.take().unwrap(), + TcpClientReader::new(socket), + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to prepare socket {:?}", err] + ) + })?, + ); + + gst_log!(CAT, obj: &self.element, "Task prepared"); + Ok(()) + } + .boxed() + } + + fn handle_action_error( + &mut self, + trigger: task::Trigger, + state: TaskState, + err: gst::ErrorMessage, + ) -> BoxFuture<'_, task::Trigger> { + async move { + match trigger { + task::Trigger::Prepare => { + gst_error!(CAT, "Task preparation failed: {:?}", err); + self.element.post_error_message(err); + + task::Trigger::Error + } + other => unreachable!("Action error for {:?} in state {:?}", other, state), + } + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = self.socket.as_mut().unwrap().next().await; + + let buffer = match item { + Some(Ok((buffer, _))) => buffer, + Some(Err(err)) => { + gst_error!(CAT, obj: &self.element, "Got error {:?}", err); + match err { + SocketError::Gst(err) => { + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + } + SocketError::Io(err) => { + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("I/O error"), + ["streaming stopped, I/O error {}", err] + ); + } + } + return Err(gst::FlowError::Error); + } + None => { + gst_log!(CAT, obj: &self.element, "SocketStream Stopped"); + return Err(gst::FlowError::Flushing); + } + }; + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let res = self + .src_pad_handler + .push_buffer(&pad, &self.element, buffer) + .await; + match res { + Ok(_) => { + gst_log!(CAT, obj: &self.element, "Successfully pushed buffer"); + } + Err(gst::FlowError::Flushing) => { + gst_debug!(CAT, obj: &self.element, "Flushing"); + } + Err(gst::FlowError::Eos) => { + gst_debug!(CAT, obj: &self.element, "EOS"); + pad.push_event(gst::event::Eos::new()).await; + } + Err(err) => { + gst_error!(CAT, obj: &self.element, "Got error {}", err); + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + } + } + + res.map(drop) + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task"); + self.src_pad_handler.reset_state().await; + gst_log!(CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } + + fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task flush"); + self.src_pad_handler.set_need_segment().await; + gst_log!(CAT, obj: &self.element, "Task flush stopped"); + Ok(()) + } + .boxed() + } +} + +pub struct TcpClientSrc { + src_pad: PadSrc, + src_pad_handler: TcpClientSrcPadHandler, + task: Task, + settings: StdMutex, +} + +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: &super::TcpClientSrc) -> 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 host: IpAddr = match settings.host { + None => { + return Err(gst_error_msg!( + gst::ResourceError::Settings, + ["No host set"] + )); + } + Some(ref host) => match host.parse() { + Err(err) => { + return Err(gst_error_msg!( + gst::ResourceError::Settings, + ["Invalid host '{}' set: {}", host, err] + )); + } + Ok(host) => host, + }, + }; + let port = settings.port; + + let buffer_pool = gst::BufferPool::new(); + let mut config = buffer_pool.get_config(); + config.set_params(None, settings.blocksize, 0, 0); + buffer_pool.set_config(config).map_err(|_| { + gst_error_msg!( + gst::ResourceError::Settings, + ["Failed to configure buffer pool"] + ) + })?; + + let saddr = SocketAddr::new(host, port as u16); + + self.src_pad_handler.prepare(settings.caps); + + self.task + .prepare( + TcpClientSrcTask::new( + element, + &self.src_pad, + &self.src_pad_handler, + saddr, + buffer_pool, + ), + context, + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_debug!(CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::TcpClientSrc) { + gst_debug!(CAT, obj: element, "Unpreparing"); + self.task.unprepare().unwrap(); + gst_debug!(CAT, obj: element, "Unprepared"); + } + + fn stop(&self, element: &super::TcpClientSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::TcpClientSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } + + fn pause(&self, element: &super::TcpClientSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Pausing"); + self.task.pause()?; + gst_debug!(CAT, obj: element, "Paused"); + Ok(()) + } +} + +impl ObjectSubclass for TcpClientSrc { + const NAME: &'static str = "RsTsTcpClientSrc"; + type Type = super::TcpClientSrc; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing TCP client source", + "Source/Network", + "Receives data over the network via TCP", + "Sebastian Dröge , LEE Dongjun ", + ); + + 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 with_class(klass: &Self::Class) -> Self { + let src_pad_handler = TcpClientSrcPadHandler::default(); + + Self { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + src_pad_handler.clone(), + ), + src_pad_handler, + task: Task::default(), + settings: StdMutex::new(Settings::default()), + } + } +} + +impl ObjectImpl for TcpClientSrc { + fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + let mut settings = self.settings.lock().unwrap(); + match *prop { + subclass::Property("host", ..) => { + settings.host = 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("blocksize", ..) => { + settings.blocksize = 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: &Self::Type, id: usize) -> Result { + 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("caps", ..) => Ok(settings.caps.to_value()), + subclass::Property("blocksize", ..) => Ok(settings.blocksize.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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SOURCE); + } +} + +impl ElementImpl for TcpClientSrc { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + 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) + } +} diff --git a/generic/threadshare/src/tcpclientsrc/mod.rs b/generic/threadshare/src/tcpclientsrc/mod.rs new file mode 100644 index 000000000..c78ba826a --- /dev/null +++ b/generic/threadshare/src/tcpclientsrc/mod.rs @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Sebastian Dröge +// Copyright (C) 2018 LEE Dongjun +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct TcpClientSrc(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for TcpClientSrc {} +unsafe impl Sync for TcpClientSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-tcpclientsrc", + gst::Rank::None, + TcpClientSrc::static_type(), + ) +} diff --git a/generic/threadshare/src/udpsink.rs b/generic/threadshare/src/udpsink.rs deleted file mode 100644 index 35ea4c599..000000000 --- a/generic/threadshare/src/udpsink.rs +++ /dev/null @@ -1,1461 +0,0 @@ -// Copyright (C) 2019 Mathieu Duponchelle -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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: i32 = 5004; -const DEFAULT_SYNC: bool = true; -const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; -const DEFAULT_BIND_PORT: i32 = 0; -const DEFAULT_BIND_ADDRESS_V6: &str = "::"; -const DEFAULT_BIND_PORT_V6: i32 = 0; -const DEFAULT_SOCKET: Option = None; -const DEFAULT_USED_SOCKET: Option = None; -const DEFAULT_SOCKET_V6: Option = None; -const DEFAULT_USED_SOCKET_V6: Option = 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 { - sync: bool, - bind_address: String, - bind_port: i32, - bind_address_v6: String, - bind_port_v6: i32, - socket: Option, - used_socket: Option, - socket_v6: Option, - used_socket_v6: Option, - 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 { - 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; 17] = [ - 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::int( - name, - "Bind Port", - "Port to bind the socket to", - 0, - u16::MAX as i32, - 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::int( - name, - "Bind Port", - "Port to bind the V6 socket to", - 0, - u16::MAX as i32, - 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, - latency: gst::ClockTime, - socket: Arc>>, - socket_v6: Arc>>, - clients: Arc>, - clients_to_configure: Vec, - clients_to_unconfigure: Vec, - sender: Arc>>>, - settings: Arc>, -} - -impl UdpSinkPadHandlerInner { - fn new(settings: Arc>) -> 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, - ) { - 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 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>); - -impl UdpSinkPadHandler { - fn new(settings: Arc>) -> 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) { - 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 add_client(&self, gst_pad: &gst::Pad, addr: SocketAddr) { - self.0.write().unwrap().add_client(gst_pad, addr); - } - - fn get_clients(&self) -> Vec { - (*self.0.read().unwrap().clients).clone() - } - - fn configure_client( - &self, - settings: &Settings, - socket: &mut Option, - socket_v6: &mut Option, - 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, - socket_v6: &mut Option, - 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 { - 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::() { - 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 = element.get_current_running_time(); - - if let Some(delay) = running_time - .saturating_sub(now) - .and_then(|delay| delay.nseconds()) - { - runtime::time::delay_for(Duration::from_nanos(delay)).await; - } - } - - async fn handle_event(&self, element: &gst::Element, event: gst::Event) { - match event.view() { - EventView::Eos(_) => { - let _ = element.post_message(gst::message::Eos::builder().src(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> { - let sender = Arc::clone(&self.0.read().unwrap().sender); - let element = element.clone(); - - async move { - if let Some(sender) = sender.lock().await.as_mut() { - if sender.send(TaskItem::Buffer(buffer)).await.is_err() { - gst_debug!(CAT, obj: &element, "Flushing"); - return Err(gst::FlowError::Flushing); - } - } - Ok(gst::FlowSuccess::Ok) - } - .boxed() - } - - fn sink_chain_list( - &self, - _pad: &PadSinkRef, - _udpsink: &UdpSink, - element: &gst::Element, - list: gst::BufferList, - ) -> BoxFuture<'static, Result> { - let sender = Arc::clone(&self.0.read().unwrap().sender); - let element = element.clone(); - - async move { - if let Some(sender) = sender.lock().await.as_mut() { - for buffer in list.iter_owned() { - if sender.send(TaskItem::Buffer(buffer)).await.is_err() { - gst_debug!(CAT, obj: &element, "Flushing"); - return Err(gst::FlowError::Flushing); - } - } - } - - 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); - return udpsink.task.flush_stop().is_ok(); - } else if let Some(sender) = sender.lock().await.as_mut() { - if sender.send(TaskItem::Event(event)).await.is_err() { - gst_debug!(CAT, obj: &element, "Flushing"); - } - } - - true - } - .boxed() - } - - fn sink_event( - &self, - _pad: &PadSinkRef, - udpsink: &UdpSink, - _element: &gst::Element, - event: gst::Event, - ) -> bool { - if let EventView::FlushStart(..) = event.view() { - return udpsink.task.flush_start().is_ok(); - } - - true - } -} - -#[derive(Debug)] -struct UdpSinkTask { - element: gst::Element, - sink_pad_handler: UdpSinkPadHandler, - receiver: Option>, -} - -impl UdpSinkTask { - fn new(element: &gst::Element, sink_pad_handler: &UdpSinkPadHandler) -> Self { - UdpSinkTask { - element: element.clone(), - sink_pad_handler: sink_pad_handler.clone(), - receiver: None, - } - } -} - -impl TaskImpl for UdpSinkTask { - fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task"); - - let (sender, receiver) = mpsc::channel(0); - - let mut sink_pad_handler = self.sink_pad_handler.0.write().unwrap(); - sink_pad_handler.sender = Arc::new(Mutex::new(Some(sender))); - - self.receiver = Some(receiver); - - gst_log!(CAT, obj: &self.element, "Task started"); - Ok(()) - } - .boxed() - } - - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - match self.receiver.as_mut().unwrap().next().await { - Some(TaskItem::Buffer(buffer)) => { - match self.sink_pad_handler.render(&self.element, buffer).await { - Err(err) => { - gst_element_error!( - &self.element, - gst::StreamError::Failed, - ["Failed to render item, stopping task: {}", err] - ); - - Err(gst::FlowError::Error) - } - _ => Ok(()), - } - } - Some(TaskItem::Event(event)) => { - self.sink_pad_handler - .handle_event(&self.element, event) - .await; - Ok(()) - } - None => Err(gst::FlowError::Flushing), - } - } - .boxed() - } -} - -#[derive(Debug)] -enum SocketFamily { - Ipv4, - Ipv6, -} - -#[derive(Debug)] -struct UdpSink { - sink_pad: PadSink, - sink_pad_handler: UdpSinkPadHandler, - task: Task, - settings: Arc>, -} - -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 socket = match family { - SocketFamily::Ipv4 => socket2::Socket::new( - socket2::Domain::ipv4(), - socket2::Type::dgram(), - Some(socket2::Protocol::udp()), - ), - SocketFamily::Ipv6 => socket2::Socket::new( - socket2::Domain::ipv6(), - socket2::Type::dgram(), - Some(socket2::Protocol::udp()), - ), - }; - - let socket = match socket { - Ok(socket) => socket, - Err(err) => { - gst_warning!( - CAT, - obj: element, - "Failed to create {} socket: {}", - match family { - SocketFamily::Ipv4 => "IPv4", - SocketFamily::Ipv6 => "IPv6", - }, - err - ); - return Ok(()); - } - }; - - socket.bind(&saddr.into()).map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenWrite, - ["Failed to bind socket: {}", err] - ) - })?; - - let socket = context.enter(|| { - tokio::net::UdpSocket::from_std(socket.into()).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(UdpSinkTask::new(&element, &self.sink_pad_handler), context) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_debug!(CAT, obj: element, "Started preparing"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - - self.task.unprepare().unwrap(); - self.sink_pad_handler.unprepare(); - - gst_debug!(CAT, obj: element, "Unprepared"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) - } -} - -impl UdpSink { - fn clear_clients(&self, clients_to_add: impl Iterator) { - 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 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: i32) -> Result { - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing UDP sink", - "Sink/Network", - "Thread-sharing UDP sink", - "Mathieu ", - ); - - 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::() - .expect("signal arg") - .expect("missing signal arg"); - let host = args[1] - .get::() - .expect("signal arg") - .expect("missing signal arg"); - let port = args[2] - .get::() - .expect("signal arg") - .expect("missing signal arg"); - - 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::() - .expect("signal arg") - .expect("missing signal arg"); - let host = args[1] - .get::() - .expect("signal arg") - .expect("missing signal arg"); - let port = args[2] - .get::() - .expect("signal arg") - .expect("missing signal arg"); - - let udpsink = Self::from_instance(&element); - - 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::() - .expect("signal arg") - .expect("missing signal arg"); - - let udpsink = Self::from_instance(&element); - udpsink.clear_clients(std::iter::empty()); - - None - }, - ); - - klass.install_properties(&PROPERTIES); - } - - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - let settings = Arc::new(StdMutex::new(Settings::default())); - let sink_pad_handler = UdpSinkPadHandler::new(Arc::clone(&settings)); - - Self { - sink_pad: PadSink::new( - gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), - sink_pad_handler.clone(), - ), - sink_pad_handler, - task: Task::default(), - settings, - } - } -} - -impl ObjectImpl for UdpSink { - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let element = obj.downcast_ref::().unwrap(); - - let mut settings = self.settings.lock().unwrap(); - match *prop { - 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::() - .expect("type checked upstream") - .map(|socket| GioSocketWrapper::new(&socket)); - } - subclass::Property("used-socket", ..) => { - unreachable!(); - } - subclass::Property("socket-v6", ..) => { - settings.socket_v6 = value - .get::() - .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 clients_iter = clients.split(',').filter_map(|client| { - let rsplit: Vec<&str> = client.rsplitn(2, ':').collect(); - - if rsplit.len() == 2 { - rsplit[0] - .parse::() - .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 - } - }); - drop(settings); - - 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 { - let prop = &PROPERTIES[id]; - - let settings = self.settings.lock().unwrap(); - match *prop { - 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", ..) => { - drop(settings); - - let clients: Vec = 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::().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_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); - } - _ => (), - } - - 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/threadshare/src/udpsink/imp.rs b/generic/threadshare/src/udpsink/imp.rs new file mode 100644 index 000000000..064d9f6c3 --- /dev/null +++ b/generic/threadshare/src/udpsink/imp.rs @@ -0,0 +1,1445 @@ +// Copyright (C) 2019 Mathieu Duponchelle +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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: i32 = 5004; +const DEFAULT_SYNC: bool = true; +const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; +const DEFAULT_BIND_PORT: i32 = 0; +const DEFAULT_BIND_ADDRESS_V6: &str = "::"; +const DEFAULT_BIND_PORT_V6: i32 = 0; +const DEFAULT_SOCKET: Option = None; +const DEFAULT_USED_SOCKET: Option = None; +const DEFAULT_SOCKET_V6: Option = None; +const DEFAULT_USED_SOCKET_V6: Option = 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 { + sync: bool, + bind_address: String, + bind_port: i32, + bind_address_v6: String, + bind_port_v6: i32, + socket: Option, + used_socket: Option, + socket_v6: Option, + used_socket_v6: Option, + 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 { + 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; 17] = [ + 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::int( + name, + "Bind Port", + "Port to bind the socket to", + 0, + u16::MAX as i32, + 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::int( + name, + "Bind Port", + "Port to bind the V6 socket to", + 0, + u16::MAX as i32, + 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, + latency: gst::ClockTime, + socket: Arc>>, + socket_v6: Arc>>, + clients: Arc>, + clients_to_configure: Vec, + clients_to_unconfigure: Vec, + sender: Arc>>>, + settings: Arc>, +} + +impl UdpSinkPadHandlerInner { + fn new(settings: Arc>) -> 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, + ) { + 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 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>); + +impl UdpSinkPadHandler { + fn new(settings: Arc>) -> 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) { + 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 add_client(&self, gst_pad: &gst::Pad, addr: SocketAddr) { + self.0.write().unwrap().add_client(gst_pad, addr); + } + + fn get_clients(&self) -> Vec { + (*self.0.read().unwrap().clients).clone() + } + + fn configure_client( + &self, + settings: &Settings, + socket: &mut Option, + socket_v6: &mut Option, + 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, + socket_v6: &mut Option, + 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: &super::UdpSink, + buffer: gst::Buffer, + ) -> Result { + 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::() { + 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: &super::UdpSink, running_time: gst::ClockTime) { + let now = element.get_current_running_time(); + + if let Some(delay) = running_time + .saturating_sub(now) + .and_then(|delay| delay.nseconds()) + { + runtime::time::delay_for(Duration::from_nanos(delay)).await; + } + } + + async fn handle_event(&self, element: &super::UdpSink, event: gst::Event) { + match event.view() { + EventView::Eos(_) => { + let _ = element.post_message(gst::message::Eos::builder().src(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> { + let sender = Arc::clone(&self.0.read().unwrap().sender); + let element = element.clone().downcast::().unwrap(); + + async move { + if let Some(sender) = sender.lock().await.as_mut() { + if sender.send(TaskItem::Buffer(buffer)).await.is_err() { + gst_debug!(CAT, obj: &element, "Flushing"); + return Err(gst::FlowError::Flushing); + } + } + Ok(gst::FlowSuccess::Ok) + } + .boxed() + } + + fn sink_chain_list( + &self, + _pad: &PadSinkRef, + _udpsink: &UdpSink, + element: &gst::Element, + list: gst::BufferList, + ) -> BoxFuture<'static, Result> { + let sender = Arc::clone(&self.0.read().unwrap().sender); + let element = element.clone().downcast::().unwrap(); + + async move { + if let Some(sender) = sender.lock().await.as_mut() { + for buffer in list.iter_owned() { + if sender.send(TaskItem::Buffer(buffer)).await.is_err() { + gst_debug!(CAT, obj: &element, "Flushing"); + return Err(gst::FlowError::Flushing); + } + } + } + + 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().downcast::().unwrap(); + + async move { + if let EventView::FlushStop(_) = event.view() { + let udpsink = UdpSink::from_instance(&element); + return udpsink.task.flush_stop().is_ok(); + } else if let Some(sender) = sender.lock().await.as_mut() { + if sender.send(TaskItem::Event(event)).await.is_err() { + gst_debug!(CAT, obj: &element, "Flushing"); + } + } + + true + } + .boxed() + } + + fn sink_event( + &self, + _pad: &PadSinkRef, + udpsink: &UdpSink, + _element: &gst::Element, + event: gst::Event, + ) -> bool { + if let EventView::FlushStart(..) = event.view() { + return udpsink.task.flush_start().is_ok(); + } + + true + } +} + +#[derive(Debug)] +struct UdpSinkTask { + element: super::UdpSink, + sink_pad_handler: UdpSinkPadHandler, + receiver: Option>, +} + +impl UdpSinkTask { + fn new(element: &super::UdpSink, sink_pad_handler: &UdpSinkPadHandler) -> Self { + UdpSinkTask { + element: element.clone(), + sink_pad_handler: sink_pad_handler.clone(), + receiver: None, + } + } +} + +impl TaskImpl for UdpSinkTask { + fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task"); + + let (sender, receiver) = mpsc::channel(0); + + let mut sink_pad_handler = self.sink_pad_handler.0.write().unwrap(); + sink_pad_handler.sender = Arc::new(Mutex::new(Some(sender))); + + self.receiver = Some(receiver); + + gst_log!(CAT, obj: &self.element, "Task started"); + Ok(()) + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + match self.receiver.as_mut().unwrap().next().await { + Some(TaskItem::Buffer(buffer)) => { + match self.sink_pad_handler.render(&self.element, buffer).await { + Err(err) => { + gst_element_error!( + &self.element, + gst::StreamError::Failed, + ["Failed to render item, stopping task: {}", err] + ); + + Err(gst::FlowError::Error) + } + _ => Ok(()), + } + } + Some(TaskItem::Event(event)) => { + self.sink_pad_handler + .handle_event(&self.element, event) + .await; + Ok(()) + } + None => Err(gst::FlowError::Flushing), + } + } + .boxed() + } +} + +#[derive(Debug)] +enum SocketFamily { + Ipv4, + Ipv6, +} + +#[derive(Debug)] +pub struct UdpSink { + sink_pad: PadSink, + sink_pad_handler: UdpSinkPadHandler, + task: Task, + settings: Arc>, +} + +impl UdpSink { + fn prepare_socket( + &self, + family: SocketFamily, + context: &Context, + element: &super::UdpSink, + ) -> 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 socket = match family { + SocketFamily::Ipv4 => socket2::Socket::new( + socket2::Domain::ipv4(), + socket2::Type::dgram(), + Some(socket2::Protocol::udp()), + ), + SocketFamily::Ipv6 => socket2::Socket::new( + socket2::Domain::ipv6(), + socket2::Type::dgram(), + Some(socket2::Protocol::udp()), + ), + }; + + let socket = match socket { + Ok(socket) => socket, + Err(err) => { + gst_warning!( + CAT, + obj: element, + "Failed to create {} socket: {}", + match family { + SocketFamily::Ipv4 => "IPv4", + SocketFamily::Ipv6 => "IPv6", + }, + err + ); + return Ok(()); + } + }; + + socket.bind(&saddr.into()).map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenWrite, + ["Failed to bind socket: {}", err] + ) + })?; + + let socket = context.enter(|| { + tokio::net::UdpSocket::from_std(socket.into()).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: &super::UdpSink) -> 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(UdpSinkTask::new(&element, &self.sink_pad_handler), context) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_debug!(CAT, obj: element, "Started preparing"); + + Ok(()) + } + + fn unprepare(&self, element: &super::UdpSink) { + gst_debug!(CAT, obj: element, "Unpreparing"); + + self.task.unprepare().unwrap(); + self.sink_pad_handler.unprepare(); + + gst_debug!(CAT, obj: element, "Unprepared"); + } + + fn stop(&self, element: &super::UdpSink) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::UdpSink) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } +} + +impl UdpSink { + fn clear_clients(&self, clients_to_add: impl Iterator) { + 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 add_client(&self, addr: SocketAddr) { + self.sink_pad_handler + .add_client(&self.sink_pad.gst_pad(), addr); + } +} + +fn try_into_socket_addr(element: &super::UdpSink, host: &str, port: i32) -> Result { + 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 Type = super::UdpSink; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing UDP sink", + "Sink/Network", + "Thread-sharing UDP sink", + "Mathieu ", + ); + + 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::() + .expect("signal arg") + .expect("missing signal arg"); + let host = args[1] + .get::() + .expect("signal arg") + .expect("missing signal arg"); + let port = args[2] + .get::() + .expect("signal arg") + .expect("missing signal arg"); + + 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::() + .expect("signal arg") + .expect("missing signal arg"); + let host = args[1] + .get::() + .expect("signal arg") + .expect("missing signal arg"); + let port = args[2] + .get::() + .expect("signal arg") + .expect("missing signal arg"); + + let udpsink = Self::from_instance(&element); + + 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::() + .expect("signal arg") + .expect("missing signal arg"); + + let udpsink = Self::from_instance(&element); + udpsink.clear_clients(std::iter::empty()); + + None + }, + ); + + klass.install_properties(&PROPERTIES); + } + + fn with_class(klass: &Self::Class) -> Self { + let settings = Arc::new(StdMutex::new(Settings::default())); + let sink_pad_handler = UdpSinkPadHandler::new(Arc::clone(&settings)); + + Self { + sink_pad: PadSink::new( + gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), + sink_pad_handler.clone(), + ), + sink_pad_handler, + task: Task::default(), + settings, + } + } +} + +impl ObjectImpl for UdpSink { + fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + let mut settings = self.settings.lock().unwrap(); + match *prop { + 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::() + .expect("type checked upstream") + .map(|socket| GioSocketWrapper::new(&socket)); + } + subclass::Property("used-socket", ..) => { + unreachable!(); + } + subclass::Property("socket-v6", ..) => { + settings.socket_v6 = value + .get::() + .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 clients_iter = clients.split(',').filter_map(|client| { + let rsplit: Vec<&str> = client.rsplitn(2, ':').collect(); + + if rsplit.len() == 2 { + rsplit[0] + .parse::() + .map_err(|err| { + gst_error!(CAT, obj: obj, "Invalid port {}: {}", rsplit[0], err); + }) + .and_then(|port| try_into_socket_addr(&obj, rsplit[1], port)) + .ok() + } else { + None + } + }); + drop(settings); + + 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: &Self::Type, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + let settings = self.settings.lock().unwrap(); + match *prop { + 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", ..) => { + drop(settings); + + let clients: Vec = 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.sink_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SINK); + } +} + +impl ElementImpl for UdpSink { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + self.parent_change_state(element, transition) + } + + fn send_event(&self, _element: &Self::Type, 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), + } + } +} diff --git a/generic/threadshare/src/udpsink/mod.rs b/generic/threadshare/src/udpsink/mod.rs new file mode 100644 index 000000000..da0729e93 --- /dev/null +++ b/generic/threadshare/src/udpsink/mod.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Mathieu Duponchelle +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct UdpSink(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for UdpSink {} +unsafe impl Sync for UdpSink {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-udpsink", + gst::Rank::None, + UdpSink::static_type(), + ) +} diff --git a/generic/threadshare/src/udpsrc.rs b/generic/threadshare/src/udpsrc.rs deleted file mode 100644 index 2572f2f0f..000000000 --- a/generic/threadshare/src/udpsrc.rs +++ /dev/null @@ -1,983 +0,0 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// 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::glib_object_subclass; -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; - -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 std::i32; -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, PadSrcWeak, Task}; - -use super::socket::{wrap_socket, GioSocketWrapper, Socket, SocketError, SocketRead}; - -const DEFAULT_ADDRESS: Option<&str> = Some("0.0.0.0"); -const DEFAULT_PORT: i32 = 5000; -const DEFAULT_REUSE: bool = true; -const DEFAULT_CAPS: Option = None; -const DEFAULT_MTU: u32 = 1492; -const DEFAULT_SOCKET: Option = None; -const DEFAULT_USED_SOCKET: Option = 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, - port: i32, // for conformity with C based udpsrc - reuse: bool, - caps: Option, - mtu: u32, - socket: Option, - used_socket: Option, - 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::int( - name, - "Port", - "Port to listen on", - 0, - u16::MAX as i32, - 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", - "Maximum expected packet size. This directly defines the allocation size of the receive buffer pool", - 0, - i32::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)] -pub struct UdpReader(tokio::net::UdpSocket); - -impl UdpReader { - fn new(socket: tokio::net::UdpSocket) -> Self { - UdpReader(socket) - } -} - -impl SocketRead for UdpReader { - const DO_TIMESTAMP: bool = true; - - fn read<'buf>( - &'buf mut self, - buffer: &'buf mut [u8], - ) -> BoxFuture<'buf, io::Result<(usize, Option)>> { - async move { - self.0 - .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, -} - -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, - configured_caps: StdMutex>, -} - -#[derive(Clone, Debug, Default)] -struct UdpSrcPadHandler(Arc); - -impl UdpSrcPadHandler { - fn prepare(&self, caps: Option, 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; - } - - async fn reset_state(&self) { - *self.0.state.lock().await = Default::default(); - } - - async fn set_need_segment(&self) { - self.0.state.lock().await.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::(), rand::random::()); - let stream_start_evt = gst::event::StreamStart::builder(&stream_id) - .group_id(gst::GroupId::next()) - .build(); - pad.push_event(stream_start_evt).await; - - if let Some(ref caps) = state.caps { - pad.push_event(gst::event::Caps::new(&caps)).await; - *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); - } - - state.need_initial_events = false; - } - - if state.need_segment { - let segment_evt = - gst::event::Segment::new(&gst::FormattedSegment::::new()); - 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_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.task.flush_start().is_ok(), - EventView::FlushStop(..) => udpsrc.task.flush_stop().is_ok(), - 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 UdpSrcTask { - element: gst::Element, - src_pad: PadSrcWeak, - src_pad_handler: UdpSrcPadHandler, - socket: Socket, -} - -impl UdpSrcTask { - fn new( - element: &gst::Element, - src_pad: &PadSrc, - src_pad_handler: &UdpSrcPadHandler, - socket: Socket, - ) -> Self { - UdpSrcTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - src_pad_handler: src_pad_handler.clone(), - socket, - } - } -} - -impl TaskImpl for UdpSrcTask { - fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Starting task"); - self.socket - .set_clock(self.element.get_clock(), Some(self.element.get_base_time())); - gst_log!(CAT, obj: &self.element, "Task started"); - Ok(()) - } - .boxed() - } - - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = self.socket.next().await; - - let (mut buffer, saddr) = match item { - Some(Ok((buffer, saddr))) => (buffer, saddr), - Some(Err(err)) => { - gst_error!(CAT, obj: &self.element, "Got error {:?}", err); - match err { - SocketError::Gst(err) => { - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - } - SocketError::Io(err) => { - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("I/O error"), - ["streaming stopped, I/O error {}", err] - ); - } - } - return Err(gst::FlowError::Error); - } - None => { - gst_log!(CAT, obj: &self.element, "SocketStream Stopped"); - return Err(gst::FlowError::Flushing); - } - }; - - if let Some(saddr) = saddr { - if self - .src_pad_handler - .0 - .state - .lock() - .await - .retrieve_sender_address - { - NetAddressMeta::add( - buffer.get_mut().unwrap(), - &gio::InetSocketAddress::from(saddr), - ); - } - } - - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let res = self - .src_pad_handler - .push_buffer(&pad, &self.element, buffer) - .await; - match res { - Ok(_) => gst_log!(CAT, obj: &self.element, "Successfully pushed buffer"), - Err(gst::FlowError::Flushing) => gst_debug!(CAT, obj: &self.element, "Flushing"), - Err(gst::FlowError::Eos) => { - gst_debug!(CAT, obj: &self.element, "EOS"); - pad.push_event(gst::event::Eos::new()).await; - } - Err(err) => { - gst_error!(CAT, obj: &self.element, "Got error {}", err); - gst_element_error!( - self.element, - gst::StreamError::Failed, - ("Internal data stream error"), - ["streaming stopped, reason {}", err] - ); - } - } - - res.map(drop) - } - .boxed() - } - - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task"); - self.src_pad_handler.reset_state().await; - gst_log!(CAT, obj: &self.element, "Task stopped"); - Ok(()) - } - .boxed() - } - - fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(CAT, obj: &self.element, "Stopping task flush"); - self.src_pad_handler.set_need_segment().await; - gst_log!(CAT, obj: &self.element, "Stopped task flush"); - Ok(()) - } - .boxed() - } -} - -struct UdpSrc { - src_pad: PadSrc, - src_pad_handler: UdpSrcPadHandler, - task: Task, - settings: StdMutex, -} - -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_guard = self.settings.lock().unwrap(); - - gst_debug!(CAT, obj: element, "Preparing"); - - let context = Context::acquire(&settings_guard.context, settings_guard.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_guard.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_guard.used_socket = Some(wrapped_socket.clone()); - - socket - } else { - let addr: IpAddr = match settings_guard.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_guard.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 socket = if addr.is_ipv4() { - socket2::Socket::new( - socket2::Domain::ipv4(), - socket2::Type::dgram(), - Some(socket2::Protocol::udp()), - ) - } else { - socket2::Socket::new( - socket2::Domain::ipv6(), - socket2::Type::dgram(), - Some(socket2::Protocol::udp()), - ) - } - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to create socket: {}", err] - ) - })?; - - socket - .set_reuse_address(settings_guard.reuse) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to set reuse_address: {}", err] - ) - })?; - - #[cfg(unix)] - { - socket.set_reuse_port(settings_guard.reuse).map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to set reuse_port: {}", err] - ) - })?; - } - - socket.bind(&saddr.into()).map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to bind socket: {}", err] - ) - })?; - - let socket = context.enter(|| { - tokio::net::UdpSocket::from_std(socket.into()).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_guard.used_socket = Some(wrap_socket(&socket)?); - - socket - }; - - let port: i32 = socket.local_addr().unwrap().port().into(); - let settings = if settings_guard.port != port { - settings_guard.port = port; - let settings = settings_guard.clone(); - drop(settings_guard); - element.notify("port"); - - settings - } else { - let settings = settings_guard.clone(); - drop(settings_guard); - - settings - }; - - 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::try_new(element.clone(), buffer_pool, UdpReader::new(socket)) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Failed to prepare socket {:?}", err] - ) - })?; - - element.notify("used-socket"); - - self.src_pad_handler - .prepare(settings.caps, settings.retrieve_sender_address); - - self.task - .prepare( - UdpSrcTask::new(element, &self.src_pad, &self.src_pad_handler, socket), - context, - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::OpenRead, - ["Error preparing Task: {:?}", err] - ) - })?; - - gst_debug!(CAT, obj: element, "Prepared"); - - Ok(()) - } - - fn unprepare(&self, element: &gst::Element) { - gst_debug!(CAT, obj: element, "Unpreparing"); - - self.settings.lock().unwrap().used_socket = None; - element.notify("used-socket"); - - self.task.unprepare().unwrap(); - - gst_debug!(CAT, obj: element, "Unprepared"); - } - - fn stop(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Stopping"); - self.task.stop()?; - gst_debug!(CAT, obj: element, "Stopped"); - Ok(()) - } - - fn start(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Starting"); - self.task.start()?; - gst_debug!(CAT, obj: element, "Started"); - Ok(()) - } - - fn pause(&self, element: &gst::Element) -> Result<(), gst::ErrorMessage> { - gst_debug!(CAT, obj: element, "Pausing"); - 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; - type Class = subclass::simple::ClassStruct; - - glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing UDP source", - "Source/Network", - "Receives data over the network via UDP", - "Sebastian Dröge ", - ); - - 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::>(); - klass.install_properties(properties.as_slice()); - } - } - - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - let src_pad_handler = UdpSrcPadHandler::default(); - - Self { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - src_pad_handler.clone(), - ), - src_pad_handler, - task: Task::default(), - settings: StdMutex::new(Settings::default()), - } - } -} - -impl ObjectImpl for UdpSrc { - 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::() - .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 { - 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::().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_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); - } - _ => (), - } - - 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/threadshare/src/udpsrc/imp.rs b/generic/threadshare/src/udpsrc/imp.rs new file mode 100644 index 000000000..f70c1e732 --- /dev/null +++ b/generic/threadshare/src/udpsrc/imp.rs @@ -0,0 +1,978 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_object_subclass; +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; + +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 std::i32; +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, PadSrcWeak, Task}; + +use crate::socket::{wrap_socket, GioSocketWrapper, Socket, SocketError, SocketRead}; + +const DEFAULT_ADDRESS: Option<&str> = Some("0.0.0.0"); +const DEFAULT_PORT: i32 = 5000; +const DEFAULT_REUSE: bool = true; +const DEFAULT_CAPS: Option = None; +const DEFAULT_MTU: u32 = 1492; +const DEFAULT_SOCKET: Option = None; +const DEFAULT_USED_SOCKET: Option = 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, + port: i32, // for conformity with C based udpsrc + reuse: bool, + caps: Option, + mtu: u32, + socket: Option, + used_socket: Option, + 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::int( + name, + "Port", + "Port to listen on", + 0, + u16::MAX as i32, + 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", + "Maximum expected packet size. This directly defines the allocation size of the receive buffer pool", + 0, + i32::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 UdpReader(tokio::net::UdpSocket); + +impl UdpReader { + fn new(socket: tokio::net::UdpSocket) -> Self { + UdpReader(socket) + } +} + +impl SocketRead for UdpReader { + const DO_TIMESTAMP: bool = true; + + fn read<'buf>( + &'buf mut self, + buffer: &'buf mut [u8], + ) -> BoxFuture<'buf, io::Result<(usize, Option)>> { + async move { + self.0 + .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, +} + +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, + configured_caps: StdMutex>, +} + +#[derive(Clone, Debug, Default)] +struct UdpSrcPadHandler(Arc); + +impl UdpSrcPadHandler { + fn prepare(&self, caps: Option, 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; + } + + async fn reset_state(&self) { + *self.0.state.lock().await = Default::default(); + } + + async fn set_need_segment(&self) { + self.0.state.lock().await.need_segment = true; + } + + async fn push_prelude(&self, pad: &PadSrcRef<'_>, _element: &super::UdpSrc) { + 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::(), rand::random::()); + let stream_start_evt = gst::event::StreamStart::builder(&stream_id) + .group_id(gst::GroupId::next()) + .build(); + pad.push_event(stream_start_evt).await; + + if let Some(ref caps) = state.caps { + pad.push_event(gst::event::Caps::new(&caps)).await; + *self.0.configured_caps.lock().unwrap() = Some(caps.clone()); + } + + state.need_initial_events = false; + } + + if state.need_segment { + let segment_evt = + gst::event::Segment::new(&gst::FormattedSegment::::new()); + pad.push_event(segment_evt).await; + + state.need_segment = false; + } + } + + async fn push_buffer( + &self, + pad: &PadSrcRef<'_>, + element: &super::UdpSrc, + buffer: gst::Buffer, + ) -> Result { + 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.task.flush_start().is_ok(), + EventView::FlushStop(..) => udpsrc.task.flush_stop().is_ok(), + 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 UdpSrcTask { + element: super::UdpSrc, + src_pad: PadSrcWeak, + src_pad_handler: UdpSrcPadHandler, + socket: Socket, +} + +impl UdpSrcTask { + fn new( + element: &super::UdpSrc, + src_pad: &PadSrc, + src_pad_handler: &UdpSrcPadHandler, + socket: Socket, + ) -> Self { + UdpSrcTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + src_pad_handler: src_pad_handler.clone(), + socket, + } + } +} + +impl TaskImpl for UdpSrcTask { + fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Starting task"); + self.socket + .set_clock(self.element.get_clock(), Some(self.element.get_base_time())); + gst_log!(CAT, obj: &self.element, "Task started"); + Ok(()) + } + .boxed() + } + + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = self.socket.next().await; + + let (mut buffer, saddr) = match item { + Some(Ok((buffer, saddr))) => (buffer, saddr), + Some(Err(err)) => { + gst_error!(CAT, obj: &self.element, "Got error {:?}", err); + match err { + SocketError::Gst(err) => { + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + } + SocketError::Io(err) => { + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("I/O error"), + ["streaming stopped, I/O error {}", err] + ); + } + } + return Err(gst::FlowError::Error); + } + None => { + gst_log!(CAT, obj: &self.element, "SocketStream Stopped"); + return Err(gst::FlowError::Flushing); + } + }; + + if let Some(saddr) = saddr { + if self + .src_pad_handler + .0 + .state + .lock() + .await + .retrieve_sender_address + { + NetAddressMeta::add( + buffer.get_mut().unwrap(), + &gio::InetSocketAddress::from(saddr), + ); + } + } + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let res = self + .src_pad_handler + .push_buffer(&pad, &self.element, buffer) + .await; + match res { + Ok(_) => gst_log!(CAT, obj: &self.element, "Successfully pushed buffer"), + Err(gst::FlowError::Flushing) => gst_debug!(CAT, obj: &self.element, "Flushing"), + Err(gst::FlowError::Eos) => { + gst_debug!(CAT, obj: &self.element, "EOS"); + pad.push_event(gst::event::Eos::new()).await; + } + Err(err) => { + gst_error!(CAT, obj: &self.element, "Got error {}", err); + gst_element_error!( + self.element, + gst::StreamError::Failed, + ("Internal data stream error"), + ["streaming stopped, reason {}", err] + ); + } + } + + res.map(drop) + } + .boxed() + } + + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task"); + self.src_pad_handler.reset_state().await; + gst_log!(CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() + } + + fn flush_stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(CAT, obj: &self.element, "Stopping task flush"); + self.src_pad_handler.set_need_segment().await; + gst_log!(CAT, obj: &self.element, "Stopped task flush"); + Ok(()) + } + .boxed() + } +} + +pub struct UdpSrc { + src_pad: PadSrc, + src_pad_handler: UdpSrcPadHandler, + task: Task, + settings: StdMutex, +} + +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: &super::UdpSrc) -> Result<(), gst::ErrorMessage> { + let mut settings_guard = self.settings.lock().unwrap(); + + gst_debug!(CAT, obj: element, "Preparing"); + + let context = Context::acquire(&settings_guard.context, settings_guard.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_guard.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_guard.used_socket = Some(wrapped_socket.clone()); + + socket + } else { + let addr: IpAddr = match settings_guard.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_guard.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 socket = if addr.is_ipv4() { + socket2::Socket::new( + socket2::Domain::ipv4(), + socket2::Type::dgram(), + Some(socket2::Protocol::udp()), + ) + } else { + socket2::Socket::new( + socket2::Domain::ipv6(), + socket2::Type::dgram(), + Some(socket2::Protocol::udp()), + ) + } + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to create socket: {}", err] + ) + })?; + + socket + .set_reuse_address(settings_guard.reuse) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to set reuse_address: {}", err] + ) + })?; + + #[cfg(unix)] + { + socket.set_reuse_port(settings_guard.reuse).map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to set reuse_port: {}", err] + ) + })?; + } + + socket.bind(&saddr.into()).map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to bind socket: {}", err] + ) + })?; + + let socket = context.enter(|| { + tokio::net::UdpSocket::from_std(socket.into()).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_guard.used_socket = Some(wrap_socket(&socket)?); + + socket + }; + + let port: i32 = socket.local_addr().unwrap().port().into(); + let settings = if settings_guard.port != port { + settings_guard.port = port; + let settings = settings_guard.clone(); + drop(settings_guard); + element.notify("port"); + + settings + } else { + let settings = settings_guard.clone(); + drop(settings_guard); + + settings + }; + + 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::try_new( + element.clone().upcast(), + buffer_pool, + UdpReader::new(socket), + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Failed to prepare socket {:?}", err] + ) + })?; + + element.notify("used-socket"); + + self.src_pad_handler + .prepare(settings.caps, settings.retrieve_sender_address); + + self.task + .prepare( + UdpSrcTask::new(element, &self.src_pad, &self.src_pad_handler, socket), + context, + ) + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::OpenRead, + ["Error preparing Task: {:?}", err] + ) + })?; + + gst_debug!(CAT, obj: element, "Prepared"); + + Ok(()) + } + + fn unprepare(&self, element: &super::UdpSrc) { + gst_debug!(CAT, obj: element, "Unpreparing"); + + self.settings.lock().unwrap().used_socket = None; + element.notify("used-socket"); + + self.task.unprepare().unwrap(); + + gst_debug!(CAT, obj: element, "Unprepared"); + } + + fn stop(&self, element: &super::UdpSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Stopping"); + self.task.stop()?; + gst_debug!(CAT, obj: element, "Stopped"); + Ok(()) + } + + fn start(&self, element: &super::UdpSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Starting"); + self.task.start()?; + gst_debug!(CAT, obj: element, "Started"); + Ok(()) + } + + fn pause(&self, element: &super::UdpSrc) -> Result<(), gst::ErrorMessage> { + gst_debug!(CAT, obj: element, "Pausing"); + self.task.pause()?; + gst_debug!(CAT, obj: element, "Paused"); + Ok(()) + } +} + +impl ObjectSubclass for UdpSrc { + const NAME: &'static str = "RsTsUdpSrc"; + type Type = super::UdpSrc; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing UDP source", + "Source/Network", + "Receives data over the network via UDP", + "Sebastian Dröge ", + ); + + 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::>(); + klass.install_properties(properties.as_slice()); + } + } + + fn with_class(klass: &Self::Class) -> Self { + let src_pad_handler = UdpSrcPadHandler::default(); + + Self { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + src_pad_handler.clone(), + ), + src_pad_handler, + task: Task::default(), + settings: StdMutex::new(Settings::default()), + } + } +} + +impl ObjectImpl for UdpSrc { + fn set_property(&self, _obj: &Self::Type, 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::() + .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: &Self::Type, id: usize) -> Result { + 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: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + + crate::set_element_flags(obj, gst::ElementFlags::SOURCE); + } +} + +impl ElementImpl for UdpSrc { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + _ => (), + } + + 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) + } +} diff --git a/generic/threadshare/src/udpsrc/mod.rs b/generic/threadshare/src/udpsrc/mod.rs new file mode 100644 index 000000000..19ebd2e1d --- /dev/null +++ b/generic/threadshare/src/udpsrc/mod.rs @@ -0,0 +1,39 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// 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::glib_wrapper; +use glib::prelude::*; + +mod imp; + +glib_wrapper! { + pub struct UdpSrc(ObjectSubclass) @extends gst::Element, gst::Object; +} + +// GStreamer elements need to be thread-safe. For the private implementation this is automatically +// enforced but for the public wrapper type we need to specify this manually. +unsafe impl Send for UdpSrc {} +unsafe impl Sync for UdpSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ts-udpsrc", + gst::Rank::None, + UdpSrc::static_type(), + ) +} diff --git a/generic/threadshare/tests/pad.rs b/generic/threadshare/tests/pad.rs index 78408d1e9..fa22175de 100644 --- a/generic/threadshare/tests/pad.rs +++ b/generic/threadshare/tests/pad.rs @@ -21,8 +21,8 @@ use futures::future::BoxFuture; use futures::lock::Mutex as FutMutex; use futures::prelude::*; -use glib::glib_object_subclass; use glib::GBoxed; +use glib::{glib_object_subclass, glib_wrapper}; use gst::prelude::*; use gst::subclass::prelude::*; @@ -53,654 +53,687 @@ fn init() { }); } -// 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, +#[derive(Debug)] +pub enum Item { + Buffer(gst::Buffer), + BufferList(gst::BufferList), + Event(gst::Event), } -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, GBoxed)] +#[gboxed(type_name = "TsTestItemSender")] +struct ItemSender { + sender: mpsc::Sender, } -#[derive(Clone, Debug)] -struct PadSrcTestHandler; +// Src +mod imp_src { + use super::*; + + 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! { + pub 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_debug!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", item); + impl PadSrcTestHandler { + async fn push_item( + pad: &PadSrcRef<'_>, + item: Item, + ) -> Result { + gst_debug!(SRC_CAT, obj: pad.gst_pad(), "Handling {:?}", item); - match item { - Item::Event(event) => { - pad.push_event(event).await; + match item { + Item::Event(event) => { + pad.push_event(event).await; - Ok(gst::FlowSuccess::Ok) + Ok(gst::FlowSuccess::Ok) + } + Item::Buffer(buffer) => pad.push(buffer).await, + Item::BufferList(list) => pad.push_list(list).await, } - 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.task.flush_start().unwrap(); - true - } - EventView::Qos(..) | EventView::Reconfigure(..) | EventView::Latency(..) => true, - EventView::FlushStop(..) => { - elem_src_test.task.flush_stop().unwrap(); - true + 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.task.flush_start().unwrap(); + true + } + EventView::Qos(..) | EventView::Reconfigure(..) | EventView::Latency(..) => true, + EventView::FlushStop(..) => { + elem_src_test.task.flush_stop().unwrap(); + 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); } - _ => 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 } - - ret } -} -#[derive(Debug)] -struct ElementSrcTestTask { - element: gst::Element, - src_pad: PadSrcWeak, - receiver: mpsc::Receiver, -} + #[derive(Debug)] + struct ElementSrcTestTask { + element: super::ElementSrcTest, + src_pad: PadSrcWeak, + receiver: mpsc::Receiver, + } -impl ElementSrcTestTask { - fn new(element: &gst::Element, src_pad: &PadSrc, receiver: mpsc::Receiver) -> Self { - ElementSrcTestTask { - element: element.clone(), - src_pad: src_pad.downgrade(), - receiver, + impl ElementSrcTestTask { + fn new( + element: &super::ElementSrcTest, + src_pad: &PadSrc, + receiver: mpsc::Receiver, + ) -> Self { + ElementSrcTestTask { + element: element.clone(), + src_pad: src_pad.downgrade(), + receiver, + } } } -} -impl ElementSrcTestTask { - fn flush(&mut self) { - // Purge the channel - while let Ok(Some(_item)) = self.receiver.try_next() {} + impl ElementSrcTestTask { + fn flush(&mut self) { + // Purge the channel + while let Ok(Some(_item)) = self.receiver.try_next() {} + } } -} - -impl TaskImpl for ElementSrcTestTask { - fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { - async move { - let item = self.receiver.next().await; - let item = match item { - Some(item) => item, - None => { - gst_log!(SRC_CAT, obj: &self.element, "SrcPad channel aborted"); - return Err(gst::FlowError::Eos); + impl TaskImpl for ElementSrcTestTask { + fn iterate(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> { + async move { + let item = self.receiver.next().await; + + let item = match item { + Some(item) => item, + None => { + gst_log!(SRC_CAT, obj: &self.element, "SrcPad channel aborted"); + return Err(gst::FlowError::Eos); + } + }; + + let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); + let res = PadSrcTestHandler::push_item(&pad, item).await; + match res { + Ok(_) => gst_log!(SRC_CAT, obj: &self.element, "Successfully pushed item"), + Err(gst::FlowError::Flushing) => { + gst_debug!(SRC_CAT, obj: &self.element, "Flushing") + } + Err(err) => panic!("Got error {}", err), } - }; - let pad = self.src_pad.upgrade().expect("PadSrc no longer exists"); - let res = PadSrcTestHandler::push_item(&pad, item).await; - match res { - Ok(_) => gst_log!(SRC_CAT, obj: &self.element, "Successfully pushed item"), - Err(gst::FlowError::Flushing) => { - gst_debug!(SRC_CAT, obj: &self.element, "Flushing") - } - Err(err) => panic!("Got error {}", err), + res.map(drop) } - - res.map(drop) + .boxed() } - .boxed() - } - fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(SRC_CAT, obj: &self.element, "Stopping task"); - self.flush(); - gst_log!(SRC_CAT, obj: &self.element, "Task stopped"); - Ok(()) + fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(SRC_CAT, obj: &self.element, "Stopping task"); + self.flush(); + gst_log!(SRC_CAT, obj: &self.element, "Task stopped"); + Ok(()) + } + .boxed() } - .boxed() - } - fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { - async move { - gst_log!(SRC_CAT, obj: &self.element, "Starting task flush"); - self.flush(); - gst_log!(SRC_CAT, obj: &self.element, "Task flush started"); - Ok(()) + fn flush_start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> { + async move { + gst_log!(SRC_CAT, obj: &self.element, "Starting task flush"); + self.flush(); + gst_log!(SRC_CAT, obj: &self.element, "Task flush started"); + Ok(()) + } + .boxed() } - .boxed() } -} -#[derive(Debug)] -struct ElementSrcTest { - src_pad: PadSrc, - task: Task, - sender: StdMutex>>, - settings: StdMutex, -} + #[derive(Debug)] + pub struct ElementSrcTest { + src_pad: PadSrc, + task: Task, + sender: StdMutex>>, + settings: StdMutex, + } -impl ElementSrcTest { - fn try_push(&self, item: Item) -> Result<(), Item> { - let state = self.task.lock_state(); - if *state != TaskState::Started && *state != TaskState::Paused { - gst_debug!(SRC_CAT, "ElementSrcTest rejecting item due to pad state"); + impl ElementSrcTest { + pub fn try_push(&self, item: Item) -> Result<(), Item> { + let state = self.task.lock_state(); + if *state != TaskState::Started && *state != TaskState::Paused { + gst_debug!(SRC_CAT, "ElementSrcTest rejecting item due to pad state"); - return Err(item); - } + 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), + 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"); + fn prepare(&self, element: &super::ElementSrcTest) -> 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] - ) - })?; + 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] + ) + })?; - let (sender, receiver) = mpsc::channel(1); - *self.sender.lock().unwrap() = Some(sender); + let (sender, receiver) = mpsc::channel(1); + *self.sender.lock().unwrap() = Some(sender); - self.task - .prepare( - ElementSrcTestTask::new(element, &self.src_pad, receiver), - context, - ) - .map_err(|err| { - gst_error_msg!( - gst::ResourceError::Failed, - ["Error preparing Task: {:?}", err] + self.task + .prepare( + ElementSrcTestTask::new(element, &self.src_pad, receiver), + context, ) - })?; + .map_err(|err| { + gst_error_msg!( + gst::ResourceError::Failed, + ["Error preparing Task: {:?}", err] + ) + })?; - gst_debug!(SRC_CAT, obj: element, "Prepared"); + gst_debug!(SRC_CAT, obj: element, "Prepared"); - Ok(()) - } + Ok(()) + } - fn unprepare(&self, element: &gst::Element) { - gst_debug!(SRC_CAT, obj: element, "Unpreparing"); + fn unprepare(&self, element: &super::ElementSrcTest) { + gst_debug!(SRC_CAT, obj: element, "Unpreparing"); - *self.sender.lock().unwrap() = None; - self.task.unprepare().unwrap(); + *self.sender.lock().unwrap() = None; + self.task.unprepare().unwrap(); - gst_debug!(SRC_CAT, obj: element, "Unprepared"); - } + gst_debug!(SRC_CAT, obj: element, "Unprepared"); + } - fn stop(&self, element: &gst::Element) { - gst_debug!(SRC_CAT, obj: element, "Stopping"); - self.task.stop().unwrap(); - gst_debug!(SRC_CAT, obj: element, "Stopped"); - } + fn stop(&self, element: &super::ElementSrcTest) { + gst_debug!(SRC_CAT, obj: element, "Stopping"); + self.task.stop().unwrap(); + gst_debug!(SRC_CAT, obj: element, "Stopped"); + } - fn start(&self, element: &gst::Element) { - gst_debug!(SRC_CAT, obj: element, "Starting"); - self.task.start().unwrap(); - gst_debug!(SRC_CAT, obj: element, "Started"); - } + fn start(&self, element: &super::ElementSrcTest) { + gst_debug!(SRC_CAT, obj: element, "Starting"); + self.task.start().unwrap(); + gst_debug!(SRC_CAT, obj: element, "Started"); + } - fn pause(&self, element: &gst::Element) { - gst_debug!(SRC_CAT, obj: element, "Pausing"); - self.task.pause().unwrap(); - gst_debug!(SRC_CAT, obj: element, "Paused"); + fn pause(&self, element: &super::ElementSrcTest) { + gst_debug!(SRC_CAT, obj: element, "Pausing"); + self.task.pause().unwrap(); + gst_debug!(SRC_CAT, obj: element, "Paused"); + } } -} - -impl ObjectSubclass for ElementSrcTest { - const NAME: &'static str = "TsElementSrcTest"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = glib::subclass::simple::ClassStruct; - glib_object_subclass!(); - - fn class_init(klass: &mut glib::subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing Test Src Element", - "Generic", - "Src Element for Pad Src Test", - "François Laignel ", - ); + impl ObjectSubclass for ElementSrcTest { + const NAME: &'static str = "TsElementSrcTest"; + type Type = super::ElementSrcTest; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = glib::subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing Test Src Element", + "Generic", + "Src Element for Pad Src Test", + "François Laignel ", + ); - 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); + 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); - } + klass.install_properties(&SRC_PROPERTIES); + } - fn with_class(klass: &glib::subclass::simple::ClassStruct) -> Self { - ElementSrcTest { - src_pad: PadSrc::new( - gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), - PadSrcTestHandler, - ), - task: Task::default(), - sender: StdMutex::new(None), - settings: StdMutex::new(Settings::default()), + fn with_class(klass: &Self::Class) -> Self { + ElementSrcTest { + src_pad: PadSrc::new( + gst::Pad::from_template(&klass.get_pad_template("src").unwrap(), Some("src")), + PadSrcTestHandler, + ), + task: Task::default(), + sender: StdMutex::new(None), + settings: StdMutex::new(Settings::default()), + } } } -} -impl ObjectImpl for ElementSrcTest { - fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &SRC_PROPERTIES[id]; + impl ObjectImpl for ElementSrcTest { + fn set_property(&self, _obj: &Self::Type, 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()); + match *prop { + glib::subclass::Property("context", ..) => { + let context = value + .get() + .expect("type checked upstream") + .unwrap_or_else(|| "".into()); - self.settings.lock().unwrap().context = context; + self.settings.lock().unwrap().context = context; + } + _ => unimplemented!(), } - _ => unimplemented!(), } - } - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); - let element = obj.downcast_ref::().unwrap(); - element.add_pad(self.src_pad.gst_pad()).unwrap(); + obj.add_pad(self.src_pad.gst_pad()).unwrap(); + } } -} -impl ElementImpl for ElementSrcTest { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - 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); - } - gst::StateChange::ReadyToNull => { - self.unprepare(element); + impl ElementImpl for ElementSrcTest { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + 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); + } + gst::StateChange::ReadyToNull => { + self.unprepare(element); + } + _ => (), } - _ => (), - } - let mut success = self.parent_change_state(element, transition)?; + let mut success = self.parent_change_state(element, transition)?; - match transition { - gst::StateChange::PausedToReady => { - self.stop(element); - } - gst::StateChange::PausedToPlaying => { - self.start(element); - } - gst::StateChange::ReadyToPaused | gst::StateChange::PlayingToPaused => { - success = gst::StateChangeSuccess::NoPreroll; + match transition { + gst::StateChange::PausedToReady => { + self.stop(element); + } + gst::StateChange::PausedToPlaying => { + self.start(element); + } + gst::StateChange::ReadyToPaused | gst::StateChange::PlayingToPaused => { + success = gst::StateChangeSuccess::NoPreroll; + } + _ => (), } - _ => (), - } - Ok(success) - } + Ok(success) + } - fn send_event(&self, _element: &gst::Element, event: gst::Event) -> bool { - match event.view() { - EventView::FlushStart(..) => { - self.task.flush_start().unwrap(); - } - EventView::FlushStop(..) => { - self.task.flush_stop().unwrap(); + fn send_event(&self, _element: &Self::Type, event: gst::Event) -> bool { + match event.view() { + EventView::FlushStart(..) => { + self.task.flush_start().unwrap(); + } + EventView::FlushStop(..) => { + self.task.flush_stop().unwrap(); + } + _ => (), } - _ => (), - } - if !event.is_serialized() { - self.src_pad.gst_pad().push_event(event) - } else { - self.try_push(Item::Event(event)).is_ok() + 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, +glib_wrapper! { + pub struct ElementSrcTest(ObjectSubclass) @extends gst::Element, gst::Object; } +unsafe impl Send for ElementSrcTest {} +unsafe impl Sync for ElementSrcTest {} -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> { - 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() - } +// Sink - fn sink_chain_list( - &self, - _pad: &PadSinkRef, - _elem_sink_test: &ElementSinkTest, - element: &gst::Element, - list: gst::BufferList, - ) -> BoxFuture<'static, Result> { - let element = element.clone(); - async move { - let elem_sink_test = ElementSinkTest::from_instance(&element); - elem_sink_test - .forward_item(&element, Item::BufferList(list)) - .await +mod imp_sink { + use super::*; + + 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> { + let element = element + .clone() + .downcast::() + .unwrap(); + async move { + let elem_sink_test = ElementSinkTest::from_instance(&element); + elem_sink_test + .forward_item(&element, Item::Buffer(buffer)) + .await + } + .boxed() } - .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 + fn sink_chain_list( + &self, + _pad: &PadSinkRef, + _elem_sink_test: &ElementSinkTest, + element: &gst::Element, + list: gst::BufferList, + ) -> BoxFuture<'static, Result> { + let element = element + .clone() + .downcast::() + .unwrap(); + async move { + let elem_sink_test = ElementSinkTest::from_instance(&element); + elem_sink_test + .forward_item(&element, Item::BufferList(list)) + .await } - _ => false, + .boxed() } - } - 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); + 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.downcast_ref::().unwrap()); + true + } + _ => false, } - - elem_sink_test - .forward_item(&element, Item::Event(event)) - .await - .is_ok() } - .boxed() - } -} -#[derive(Debug)] -struct ElementSinkTest { - sink_pad: PadSink, - flushing: AtomicBool, - sender: FutMutex>>, -} + 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() + .downcast::() + .unwrap(); + async move { + let elem_sink_test = ElementSinkTest::from_instance(&element); + + if let EventView::FlushStop(..) = event.view() { + elem_sink_test.start(&element); + } -impl ElementSinkTest { - async fn forward_item( - &self, - element: &gst::Element, - item: Item, - ) -> Result { - 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) + elem_sink_test + .forward_item(&element, Item::Event(event)) + .await + .is_ok() + } + .boxed() } } - 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"); + #[derive(Debug)] + pub struct ElementSinkTest { + sink_pad: PadSink, + flushing: AtomicBool, + sender: FutMutex>>, } - 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 { + async fn forward_item( + &self, + element: &super::ElementSinkTest, + item: Item, + ) -> Result { + 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) + } + } -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::FlushStart::new()); - gst_debug!(SINK_CAT, obj: element, "FlushStart pushed"); - } + fn start(&self, element: &super::ElementSinkTest) { + gst_debug!(SINK_CAT, obj: element, "Starting"); + self.flushing.store(false, Ordering::SeqCst); + gst_debug!(SINK_CAT, obj: element, "Started"); + } - 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::FlushStop::new(true)); - gst_debug!(SINK_CAT, obj: element, "FlushStop pushed"); + fn stop(&self, element: &super::ElementSinkTest) { + gst_debug!(SINK_CAT, obj: element, "Stopping"); + self.flushing.store(true, Ordering::SeqCst); + gst_debug!(SINK_CAT, obj: element, "Stopped"); + } } -} - -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; - type Class = glib::subclass::simple::ClassStruct; + impl ElementSinkTest { + pub fn push_flush_start(&self, element: &super::ElementSinkTest) { + gst_debug!(SINK_CAT, obj: element, "Pushing FlushStart"); + self.sink_pad + .gst_pad() + .push_event(gst::event::FlushStart::new()); + gst_debug!(SINK_CAT, obj: element, "FlushStart pushed"); + } - glib_object_subclass!(); + pub fn push_flush_stop(&self, element: &super::ElementSinkTest) { + gst_debug!(SINK_CAT, obj: element, "Pushing FlushStop"); + self.sink_pad + .gst_pad() + .push_event(gst::event::FlushStop::new(true)); + gst_debug!(SINK_CAT, obj: element, "FlushStop pushed"); + } + } - fn class_init(klass: &mut glib::subclass::simple::ClassStruct) { - klass.set_metadata( - "Thread-sharing Test Sink Element", - "Generic", - "Sink Element for Pad Test", - "François Laignel ", + lazy_static! { + static ref SINK_CAT: gst::DebugCategory = gst::DebugCategory::new( + "ts-element-sink-test", + gst::DebugColorFlags::empty(), + Some("Thread-sharing Test Sink Element"), ); + } - 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); + impl ObjectSubclass for ElementSinkTest { + const NAME: &'static str = "TsElementSinkTest"; + type Type = super::ElementSinkTest; + type ParentType = gst::Element; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = glib::subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_metadata( + "Thread-sharing Test Sink Element", + "Generic", + "Sink Element for Pad Test", + "François Laignel ", + ); - klass.install_properties(&SINK_PROPERTIES); - } + 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); - fn with_class(klass: &glib::subclass::simple::ClassStruct) -> Self { - ElementSinkTest { - sink_pad: PadSink::new( - gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), - PadSinkTestHandler, - ), - flushing: AtomicBool::new(true), - sender: FutMutex::new(None), + klass.install_properties(&SINK_PROPERTIES); } - } -} -impl ObjectImpl for ElementSinkTest { - 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); + fn with_class(klass: &Self::Class) -> Self { + ElementSinkTest { + sink_pad: PadSink::new( + gst::Pad::from_template(&klass.get_pad_template("sink").unwrap(), Some("sink")), + PadSinkTestHandler, + ), + flushing: AtomicBool::new(true), + sender: FutMutex::new(None), } - _ => unimplemented!(), } } - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); + impl ObjectImpl for ElementSinkTest { + fn set_property(&self, _obj: &Self::Type, 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!(), + } + } - let element = obj.downcast_ref::().unwrap(); - element.add_pad(self.sink_pad.gst_pad()).unwrap(); + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(self.sink_pad.gst_pad()).unwrap(); + } } -} -impl ElementImpl for ElementSinkTest { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - gst_log!(SINK_CAT, obj: element, "Changing state {:?}", transition); + impl ElementImpl for ElementSinkTest { + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + gst_log!(SINK_CAT, obj: element, "Changing state {:?}", transition); - if let gst::StateChange::PausedToReady = transition { - self.stop(element); - } + if let gst::StateChange::PausedToReady = transition { + self.stop(element); + } - let success = self.parent_change_state(element, transition)?; + let success = self.parent_change_state(element, transition)?; - if let gst::StateChange::ReadyToPaused = transition { - self.start(element); - } + if let gst::StateChange::ReadyToPaused = transition { + self.start(element); + } - Ok(success) + Ok(success) + } } } +glib_wrapper! { + pub struct ElementSinkTest(ObjectSubclass) @extends gst::Element, gst::Object; +} +unsafe impl Send for ElementSinkTest {} +unsafe impl Sync for ElementSinkTest {} + fn setup( context_name: &str, mut middle_element_1: Option, mut middle_element_2: Option, ) -> ( gst::Pipeline, - gst::Element, - gst::Element, + ElementSrcTest, + ElementSinkTest, mpsc::Receiver, ) { init(); @@ -708,14 +741,14 @@ fn setup( let pipeline = gst::Pipeline::new(None); // Src - let src_element = glib::Object::new(ElementSrcTest::get_type(), &[]) + let src_element = glib::Object::new(ElementSrcTest::static_type(), &[]) .unwrap() - .downcast::() + .downcast::() .unwrap(); src_element.set_property("context", &context_name).unwrap(); pipeline.add(&src_element).unwrap(); - let mut last_element = src_element.clone(); + let mut last_element = src_element.clone().upcast::(); if let Some(middle_element) = middle_element_1.take() { pipeline.add(&middle_element).unwrap(); @@ -730,9 +763,9 @@ fn setup( } // Sink - let sink_element = glib::Object::new(ElementSinkTest::get_type(), &[]) + let sink_element = glib::Object::new(ElementSinkTest::static_type(), &[]) .unwrap() - .downcast::() + .downcast::() .unwrap(); pipeline.add(&sink_element).unwrap(); last_element.link(&sink_element).unwrap(); @@ -748,10 +781,10 @@ fn setup( fn nominal_scenario( scenario_name: &str, pipeline: gst::Pipeline, - src_element: gst::Element, + src_element: ElementSrcTest, mut receiver: mpsc::Receiver, ) { - let elem_src_test = ElementSrcTest::from_instance(&src_element); + let elem_src_test = imp_src::ElementSrcTest::from_instance(&src_element); pipeline.set_state(gst::State::Playing).unwrap(); @@ -975,7 +1008,7 @@ fn 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); + let elem_src_test = imp_src::ElementSrcTest::from_instance(&src_element); pipeline.set_state(gst::State::Playing).unwrap(); @@ -1053,7 +1086,7 @@ fn 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); + let elem_src_test = imp_src::ElementSrcTest::from_instance(&src_element); pipeline.set_state(gst::State::Playing).unwrap(); @@ -1109,7 +1142,7 @@ fn start_stop_start() { match futures::executor::block_on(receiver.next()).unwrap() { Item::Buffer(_buffer) => { gst_info!( - SRC_CAT, + imp_src::SRC_CAT, "{}: initial buffer went through, don't expect any pending item to be dropped", scenario_name ); @@ -1166,7 +1199,7 @@ fn start_flush() { let (pipeline, src_element, sink_element, mut receiver) = setup(&scenario_name, None, None); - let elem_src_test = ElementSrcTest::from_instance(&src_element); + let elem_src_test = imp_src::ElementSrcTest::from_instance(&src_element); pipeline.set_state(gst::State::Playing).unwrap(); @@ -1206,7 +1239,7 @@ fn start_flush() { .try_push(Item::Buffer(gst::Buffer::from_slice(vec![1, 2, 3, 4]))) .unwrap(); - let elem_sink_test = ElementSinkTest::from_instance(&sink_element); + let elem_sink_test = imp_sink::ElementSinkTest::from_instance(&sink_element); elem_sink_test.push_flush_start(&sink_element); -- cgit v1.2.3