2020-01-16 08:27:50 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
use gst::subclass::prelude::*;
|
2021-02-23 10:37:24 +00:00
|
|
|
use gst::{gst_error, gst_log, gst_trace};
|
2020-01-16 08:27:50 +00:00
|
|
|
|
|
|
|
use once_cell::sync::OnceCell;
|
|
|
|
|
|
|
|
use std::sync::atomic;
|
|
|
|
use std::sync::Mutex;
|
|
|
|
use std::thread;
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
|
2020-01-16 08:27:50 +00:00
|
|
|
use crate::ndi;
|
|
|
|
|
2021-09-29 10:45:04 +00:00
|
|
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|
|
|
gst::DebugCategory::new(
|
|
|
|
"ndideviceprovider",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("NewTek NDI Device Provider"),
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
2020-01-16 08:27:50 +00:00
|
|
|
#[derive(Debug)]
|
2021-09-13 09:26:56 +00:00
|
|
|
pub struct DeviceProvider {
|
2020-01-16 08:27:50 +00:00
|
|
|
thread: Mutex<Option<thread::JoinHandle<()>>>,
|
2021-09-13 09:26:56 +00:00
|
|
|
current_devices: Mutex<Vec<super::Device>>,
|
2020-01-16 08:27:50 +00:00
|
|
|
find: Mutex<Option<ndi::FindInstance>>,
|
|
|
|
is_running: atomic::AtomicBool,
|
|
|
|
}
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
#[glib::object_subclass]
|
2020-01-16 08:27:50 +00:00
|
|
|
impl ObjectSubclass for DeviceProvider {
|
|
|
|
const NAME: &'static str = "NdiDeviceProvider";
|
2021-09-13 09:26:56 +00:00
|
|
|
type Type = super::DeviceProvider;
|
2020-01-16 08:27:50 +00:00
|
|
|
type ParentType = gst::DeviceProvider;
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
thread: Mutex::new(None),
|
|
|
|
current_devices: Mutex::new(vec![]),
|
|
|
|
find: Mutex::new(None),
|
|
|
|
is_running: atomic::AtomicBool::new(false),
|
|
|
|
}
|
|
|
|
}
|
2021-09-13 09:26:56 +00:00
|
|
|
}
|
2020-01-16 08:27:50 +00:00
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
impl ObjectImpl for DeviceProvider {}
|
|
|
|
|
2022-01-22 10:12:03 +00:00
|
|
|
impl GstObjectImpl for DeviceProvider {}
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
impl DeviceProviderImpl for DeviceProvider {
|
|
|
|
fn metadata() -> Option<&'static gst::subclass::DeviceProviderMetadata> {
|
|
|
|
static METADATA: Lazy<gst::subclass::DeviceProviderMetadata> = Lazy::new(|| {
|
|
|
|
gst::subclass::DeviceProviderMetadata::new("NewTek NDI Device Provider",
|
2020-01-16 08:27:50 +00:00
|
|
|
"Source/Audio/Video/Network",
|
|
|
|
"NewTek NDI Device Provider",
|
2021-09-13 09:26:56 +00:00
|
|
|
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>")
|
|
|
|
});
|
2020-01-16 08:27:50 +00:00
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
Some(&*METADATA)
|
|
|
|
}
|
2020-01-16 08:27:50 +00:00
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
fn probe(&self, _device_provider: &Self::Type) -> Vec<gst::Device> {
|
|
|
|
self.current_devices
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|d| d.clone().upcast())
|
|
|
|
.collect()
|
2020-01-16 08:27:50 +00:00
|
|
|
}
|
2021-09-13 09:26:56 +00:00
|
|
|
|
|
|
|
fn start(&self, device_provider: &Self::Type) -> Result<(), gst::LoggableError> {
|
2020-01-16 08:27:50 +00:00
|
|
|
let mut thread_guard = self.thread.lock().unwrap();
|
|
|
|
if thread_guard.is_some() {
|
2021-09-29 10:45:04 +00:00
|
|
|
gst_log!(CAT, obj: device_provider, "Device provider already started");
|
2020-01-16 08:27:50 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.is_running.store(true, atomic::Ordering::SeqCst);
|
|
|
|
|
|
|
|
let device_provider_weak = device_provider.downgrade();
|
|
|
|
let mut first = true;
|
|
|
|
*thread_guard = Some(thread::spawn(move || {
|
|
|
|
let device_provider = match device_provider_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(device_provider) => device_provider,
|
|
|
|
};
|
|
|
|
|
|
|
|
let imp = DeviceProvider::from_instance(&device_provider);
|
|
|
|
{
|
|
|
|
let mut find_guard = imp.find.lock().unwrap();
|
|
|
|
if find_guard.is_some() {
|
2021-09-29 10:45:04 +00:00
|
|
|
gst_log!(CAT, obj: &device_provider, "Already started");
|
2020-01-16 08:27:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let find = match ndi::FindInstance::builder().build() {
|
|
|
|
None => {
|
2021-09-29 10:45:04 +00:00
|
|
|
gst_error!(CAT, obj: &device_provider, "Failed to create Find instance");
|
2020-01-16 08:27:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(find) => find,
|
|
|
|
};
|
|
|
|
*find_guard = Some(find);
|
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let device_provider = match device_provider_weak.upgrade() {
|
|
|
|
None => break,
|
|
|
|
Some(device_provider) => device_provider,
|
|
|
|
};
|
|
|
|
|
|
|
|
let imp = DeviceProvider::from_instance(&device_provider);
|
|
|
|
if !imp.is_running.load(atomic::Ordering::SeqCst) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
imp.poll(&device_provider, first);
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-09-13 09:26:56 +00:00
|
|
|
|
|
|
|
fn stop(&self, _device_provider: &Self::Type) {
|
2020-01-16 08:27:50 +00:00
|
|
|
if let Some(_thread) = self.thread.lock().unwrap().take() {
|
|
|
|
self.is_running.store(false, atomic::Ordering::SeqCst);
|
|
|
|
// Don't actually join because that might take a while
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceProvider {
|
2021-09-13 09:26:56 +00:00
|
|
|
fn poll(&self, device_provider: &super::DeviceProvider, first: bool) {
|
2020-01-16 08:27:50 +00:00
|
|
|
let mut find_guard = self.find.lock().unwrap();
|
|
|
|
let find = match *find_guard {
|
|
|
|
None => return,
|
|
|
|
Some(ref mut find) => find,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !find.wait_for_sources(if first { 1000 } else { 5000 }) {
|
2021-09-29 10:45:04 +00:00
|
|
|
gst_trace!(CAT, obj: device_provider, "No new sources found");
|
2020-01-16 08:27:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let sources = find.get_current_sources();
|
|
|
|
let mut sources = sources.iter().map(|s| s.to_owned()).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let mut current_devices_guard = self.current_devices.lock().unwrap();
|
|
|
|
let mut expired_devices = vec![];
|
|
|
|
let mut remaining_sources = vec![];
|
|
|
|
|
|
|
|
// First check for each device we previously knew if it's still available
|
|
|
|
for old_device in &*current_devices_guard {
|
|
|
|
let old_device_imp = Device::from_instance(old_device);
|
|
|
|
let old_source = old_device_imp.source.get().unwrap();
|
|
|
|
|
2021-09-29 10:45:04 +00:00
|
|
|
if !sources.contains(&*old_source) {
|
2020-01-16 08:27:50 +00:00
|
|
|
gst_log!(
|
2021-09-29 10:45:04 +00:00
|
|
|
CAT,
|
2020-01-16 08:27:50 +00:00
|
|
|
obj: device_provider,
|
|
|
|
"Source {:?} disappeared",
|
|
|
|
old_source
|
|
|
|
);
|
|
|
|
expired_devices.push(old_device.clone());
|
|
|
|
} else {
|
|
|
|
// Otherwise remember that we had it before already and don't have to announce it
|
|
|
|
// again. After the loop we're going to remove these all from the sources vec.
|
2021-09-29 10:45:04 +00:00
|
|
|
remaining_sources.push(old_source.to_owned());
|
2020-01-16 08:27:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for remaining_source in remaining_sources {
|
|
|
|
sources.retain(|s| s != &remaining_source);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all expired devices from the list of cached devices
|
|
|
|
current_devices_guard.retain(|d| !expired_devices.contains(d));
|
|
|
|
// And also notify the device provider of them having disappeared
|
|
|
|
for old_device in expired_devices {
|
|
|
|
device_provider.device_remove(&old_device);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now go through all new devices and announce them
|
|
|
|
for source in sources {
|
2021-09-29 10:45:04 +00:00
|
|
|
gst_log!(CAT, obj: device_provider, "Source {:?} appeared", source);
|
|
|
|
let device = super::Device::new(&source);
|
2020-01-16 08:27:50 +00:00
|
|
|
device_provider.device_add(&device);
|
|
|
|
current_devices_guard.push(device);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2021-09-13 09:26:56 +00:00
|
|
|
pub struct Device {
|
2021-09-29 10:45:04 +00:00
|
|
|
source: OnceCell<ndi::Source<'static>>,
|
2020-01-16 08:27:50 +00:00
|
|
|
}
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
#[glib::object_subclass]
|
2020-01-16 08:27:50 +00:00
|
|
|
impl ObjectSubclass for Device {
|
|
|
|
const NAME: &'static str = "NdiDevice";
|
2021-09-13 09:26:56 +00:00
|
|
|
type Type = super::Device;
|
2020-01-16 08:27:50 +00:00
|
|
|
type ParentType = gst::Device;
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
source: OnceCell::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
impl ObjectImpl for Device {}
|
2020-01-16 08:27:50 +00:00
|
|
|
|
2022-01-22 10:12:03 +00:00
|
|
|
impl GstObjectImpl for Device {}
|
|
|
|
|
2020-01-16 08:27:50 +00:00
|
|
|
impl DeviceImpl for Device {
|
|
|
|
fn create_element(
|
|
|
|
&self,
|
2021-09-13 09:26:56 +00:00
|
|
|
_device: &Self::Type,
|
2020-01-16 08:27:50 +00:00
|
|
|
name: Option<&str>,
|
|
|
|
) -> Result<gst::Element, gst::LoggableError> {
|
|
|
|
let source_info = self.source.get().unwrap();
|
2021-09-13 09:26:56 +00:00
|
|
|
let element = glib::Object::with_type(
|
2021-09-29 10:45:04 +00:00
|
|
|
crate::ndisrc::NdiSrc::static_type(),
|
2020-01-16 08:27:50 +00:00
|
|
|
&[
|
|
|
|
("name", &name),
|
2021-09-29 10:45:04 +00:00
|
|
|
("ndi-name", &source_info.ndi_name()),
|
|
|
|
("url-address", &source_info.url_address()),
|
2020-01-16 08:27:50 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
.dynamic_cast::<gst::Element>()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
Ok(element)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
impl super::Device {
|
2021-09-29 10:45:04 +00:00
|
|
|
fn new(source: &ndi::Source<'_>) -> super::Device {
|
|
|
|
let display_name = source.ndi_name();
|
|
|
|
let device_class = "Source/Audio/Video/Network";
|
|
|
|
|
|
|
|
let element_class =
|
|
|
|
glib::Class::<gst::Element>::from_type(crate::ndisrc::NdiSrc::static_type()).unwrap();
|
2021-09-13 09:26:56 +00:00
|
|
|
let templ = element_class.pad_template("src").unwrap();
|
|
|
|
let caps = templ.caps();
|
2020-01-16 08:27:50 +00:00
|
|
|
|
|
|
|
// Put the url-address into the extra properties
|
|
|
|
let extra_properties = gst::Structure::builder("properties")
|
|
|
|
.field("ndi-name", &source.ndi_name())
|
|
|
|
.field("url-address", &source.url_address())
|
|
|
|
.build();
|
|
|
|
|
2021-09-13 09:26:56 +00:00
|
|
|
let device = glib::Object::new::<super::Device>(&[
|
|
|
|
("caps", &caps),
|
|
|
|
("display-name", &display_name),
|
|
|
|
("device-class", &device_class),
|
|
|
|
("properties", &extra_properties),
|
|
|
|
])
|
2020-01-16 08:27:50 +00:00
|
|
|
.unwrap();
|
|
|
|
let device_impl = Device::from_instance(&device);
|
|
|
|
|
2021-09-29 10:45:04 +00:00
|
|
|
device_impl.source.set(source.to_owned()).unwrap();
|
2020-01-16 08:27:50 +00:00
|
|
|
|
|
|
|
device
|
|
|
|
}
|
|
|
|
}
|