2018-04-09 05:53:04 +00:00
|
|
|
#[macro_use]
|
2018-12-11 16:47:03 +00:00
|
|
|
extern crate glib;
|
2019-07-15 14:56:29 +00:00
|
|
|
use glib::prelude::*;
|
|
|
|
use glib::subclass::prelude::*;
|
2018-04-09 05:53:04 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate gstreamer as gst;
|
|
|
|
extern crate gstreamer_audio as gst_audio;
|
|
|
|
extern crate gstreamer_base as gst_base;
|
|
|
|
extern crate gstreamer_video as gst_video;
|
|
|
|
|
2018-08-14 13:45:13 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
2018-12-12 11:00:33 +00:00
|
|
|
extern crate byte_slice_cast;
|
2018-04-09 05:53:04 +00:00
|
|
|
|
2019-07-11 17:35:43 +00:00
|
|
|
pub mod ndi;
|
|
|
|
mod ndiaudiosrc;
|
2019-07-15 16:43:22 +00:00
|
|
|
pub mod ndisys;
|
2018-09-18 09:53:12 +00:00
|
|
|
mod ndivideosrc;
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
use ndi::*;
|
2019-07-15 16:43:22 +00:00
|
|
|
use ndisys::*;
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2018-08-14 13:45:13 +00:00
|
|
|
use std::collections::HashMap;
|
2019-07-11 18:56:30 +00:00
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
2019-07-15 16:43:22 +00:00
|
|
|
use std::sync::Mutex;
|
2018-08-14 14:47:58 +00:00
|
|
|
|
2019-07-16 14:43:03 +00:00
|
|
|
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
|
|
|
|
#[repr(u32)]
|
|
|
|
pub enum TimestampMode {
|
|
|
|
ReceiveTime = 0,
|
|
|
|
Timecode = 1,
|
|
|
|
Timestamp = 2,
|
|
|
|
}
|
|
|
|
|
2018-12-11 16:47:03 +00:00
|
|
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
2019-07-11 18:56:30 +00:00
|
|
|
if !ndi::initialize() {
|
|
|
|
return Err(glib_bool_error!("Cannot initialize NDI"));
|
|
|
|
}
|
|
|
|
|
2018-12-11 16:47:03 +00:00
|
|
|
ndivideosrc::register(plugin)?;
|
|
|
|
ndiaudiosrc::register(plugin)?;
|
|
|
|
Ok(())
|
2018-04-09 05:53:04 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
struct ReceiverInfo {
|
|
|
|
id: usize,
|
2018-08-20 07:25:15 +00:00
|
|
|
stream_name: String,
|
|
|
|
ip: String,
|
|
|
|
video: bool,
|
|
|
|
audio: bool,
|
2019-07-11 18:56:30 +00:00
|
|
|
ndi_instance: RecvInstance,
|
2018-08-20 07:25:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-14 13:45:13 +00:00
|
|
|
lazy_static! {
|
2019-07-11 18:56:30 +00:00
|
|
|
static ref HASHMAP_RECEIVERS: Mutex<HashMap<usize, ReceiverInfo>> = {
|
2018-08-20 10:14:54 +00:00
|
|
|
let m = HashMap::new();
|
2018-08-14 13:45:13 +00:00
|
|
|
Mutex::new(m)
|
|
|
|
};
|
2019-07-16 13:03:15 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "reference-timestamps")]
|
|
|
|
static ref TIMECODE_CAPS: gst::Caps = {
|
|
|
|
gst::Caps::new_simple("timestamp/x-ndi-timecode", &[])
|
|
|
|
};
|
|
|
|
|
|
|
|
#[cfg(feature = "reference-timestamps")]
|
|
|
|
static ref TIMESTAMP_CAPS: gst::Caps = {
|
|
|
|
gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[])
|
|
|
|
};
|
2018-08-14 13:45:13 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0);
|
2018-08-20 07:25:15 +00:00
|
|
|
|
2019-03-26 16:41:28 +00:00
|
|
|
fn connect_ndi(
|
|
|
|
cat: gst::DebugCategory,
|
|
|
|
element: &gst_base::BaseSrc,
|
|
|
|
ip: &str,
|
|
|
|
stream_name: &str,
|
2019-07-11 18:56:30 +00:00
|
|
|
) -> Option<usize> {
|
2018-09-12 07:44:46 +00:00
|
|
|
gst_debug!(cat, obj: element, "Starting NDI connection...");
|
2018-08-14 14:47:58 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
let mut receivers = HASHMAP_RECEIVERS.lock().unwrap();
|
2018-08-20 07:25:15 +00:00
|
|
|
|
2019-07-15 14:56:29 +00:00
|
|
|
let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type();
|
2018-08-14 14:47:58 +00:00
|
|
|
|
2018-09-18 09:53:12 +00:00
|
|
|
for val in receivers.values_mut() {
|
|
|
|
if val.ip == ip || val.stream_name == stream_name {
|
2019-07-15 16:46:22 +00:00
|
|
|
if (val.video || !video) && (val.audio || video) {
|
2018-09-12 07:44:46 +00:00
|
|
|
continue;
|
2018-09-18 09:53:12 +00:00
|
|
|
} else {
|
2018-09-12 07:44:46 +00:00
|
|
|
if video {
|
2019-07-15 16:46:22 +00:00
|
|
|
val.video = true;
|
2018-09-18 09:53:12 +00:00
|
|
|
} else {
|
2019-07-15 16:46:22 +00:00
|
|
|
val.audio = true;
|
2018-08-20 07:25:15 +00:00
|
|
|
}
|
2019-07-11 18:56:30 +00:00
|
|
|
return Some(val.id);
|
2018-08-14 14:47:58 +00:00
|
|
|
}
|
2018-08-20 07:25:15 +00:00
|
|
|
}
|
2018-09-12 07:44:46 +00:00
|
|
|
}
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
let mut find = match FindInstance::builder().build() {
|
|
|
|
None => {
|
2018-09-18 09:53:12 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Negotiation,
|
|
|
|
["Cannot run NDI: NDIlib_find_create_v2 error"]
|
|
|
|
);
|
2019-07-11 18:56:30 +00:00
|
|
|
return None;
|
2019-07-15 16:43:22 +00:00
|
|
|
}
|
2019-07-11 18:56:30 +00:00
|
|
|
Some(find) => find,
|
|
|
|
};
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
// TODO Sleep 1s to wait for all sources
|
|
|
|
find.wait_for_sources(2000);
|
2018-06-27 09:56:11 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
let sources = find.get_current_sources();
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
// We need at least one source
|
|
|
|
if sources.is_empty() {
|
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Negotiation,
|
|
|
|
["Error getting NDIlib_find_get_current_sources"]
|
2018-09-18 09:53:12 +00:00
|
|
|
);
|
2019-07-11 18:56:30 +00:00
|
|
|
return None;
|
|
|
|
}
|
2018-06-27 09:56:11 +00:00
|
|
|
|
2019-07-15 16:43:22 +00:00
|
|
|
let source = sources
|
|
|
|
.iter()
|
|
|
|
.find(|s| s.ndi_name() == stream_name || s.ip_address() == ip);
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
let source = match source {
|
|
|
|
None => {
|
|
|
|
gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]);
|
|
|
|
return None;
|
2019-07-15 16:43:22 +00:00
|
|
|
}
|
2019-07-11 18:56:30 +00:00
|
|
|
Some(source) => source,
|
|
|
|
};
|
2018-06-27 09:56:11 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
gst_debug!(
|
|
|
|
cat,
|
|
|
|
obj: element,
|
|
|
|
"Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'",
|
|
|
|
sources.len(),
|
|
|
|
source.ndi_name(),
|
|
|
|
source.ip_address(),
|
|
|
|
);
|
|
|
|
|
2019-07-16 09:14:37 +00:00
|
|
|
// FIXME: Property for the name and bandwidth
|
|
|
|
// FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be
|
|
|
|
// broken with interlaced content currently
|
2019-07-11 18:56:30 +00:00
|
|
|
let recv = RecvInstance::builder(&source, "Galicaster NDI Receiver")
|
|
|
|
.bandwidth(NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest)
|
|
|
|
.color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA)
|
2019-07-16 09:14:37 +00:00
|
|
|
.allow_video_fields(true)
|
2019-07-11 18:56:30 +00:00
|
|
|
.build();
|
|
|
|
let recv = match recv {
|
|
|
|
None => {
|
2018-09-18 09:53:12 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Negotiation,
|
|
|
|
["Cannot run NDI: NDIlib_recv_create_v3 error"]
|
|
|
|
);
|
2019-07-11 18:56:30 +00:00
|
|
|
return None;
|
2019-07-15 16:43:22 +00:00
|
|
|
}
|
2019-07-11 18:56:30 +00:00
|
|
|
Some(recv) => recv,
|
|
|
|
};
|
2018-06-27 09:56:11 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
recv.set_tally(&Tally::default());
|
|
|
|
|
|
|
|
let enable_hw_accel = MetadataFrame::new(0, Some("<ndi_hwaccel enabled=\"true\"/>"));
|
|
|
|
recv.send_metadata(&enable_hw_accel);
|
|
|
|
|
|
|
|
let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst);
|
|
|
|
receivers.insert(
|
|
|
|
id_receiver,
|
|
|
|
ReceiverInfo {
|
|
|
|
stream_name: source.ndi_name().to_owned(),
|
|
|
|
ip: source.ip_address().to_owned(),
|
|
|
|
video,
|
2019-07-15 14:56:29 +00:00
|
|
|
audio: !video,
|
2019-07-11 18:56:30 +00:00
|
|
|
ndi_instance: recv,
|
|
|
|
id: id_receiver,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
gst_debug!(cat, obj: element, "Started NDI connection");
|
|
|
|
Some(id_receiver)
|
2018-06-27 09:56:11 +00:00
|
|
|
}
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-11 18:56:30 +00:00
|
|
|
fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: usize) -> bool {
|
2018-06-27 09:56:11 +00:00
|
|
|
gst_debug!(cat, obj: element, "Closing NDI connection...");
|
2019-07-11 18:56:30 +00:00
|
|
|
let mut receivers = HASHMAP_RECEIVERS.lock().unwrap();
|
2018-09-12 07:44:46 +00:00
|
|
|
{
|
|
|
|
let val = receivers.get_mut(&id).unwrap();
|
2018-09-18 09:53:12 +00:00
|
|
|
if val.video && val.audio {
|
2019-07-15 14:56:29 +00:00
|
|
|
let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type();
|
|
|
|
if video {
|
2018-09-12 07:44:46 +00:00
|
|
|
val.video = false;
|
2019-07-15 14:56:29 +00:00
|
|
|
} else {
|
|
|
|
val.audio = false;
|
2018-09-12 07:44:46 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2018-06-25 08:38:45 +00:00
|
|
|
}
|
2018-09-12 07:44:46 +00:00
|
|
|
receivers.remove(&id);
|
|
|
|
gst_debug!(cat, obj: element, "Closed NDI connection");
|
2018-09-18 11:12:04 +00:00
|
|
|
true
|
2018-06-27 09:56:11 +00:00
|
|
|
}
|
2018-06-25 08:38:45 +00:00
|
|
|
|
2019-07-16 14:43:03 +00:00
|
|
|
impl glib::translate::ToGlib for TimestampMode {
|
|
|
|
type GlibType = i32;
|
|
|
|
|
|
|
|
fn to_glib(&self) -> i32 {
|
|
|
|
*self as i32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl glib::translate::FromGlib<i32> for TimestampMode {
|
|
|
|
fn from_glib(value: i32) -> Self {
|
|
|
|
match value {
|
|
|
|
0 => TimestampMode::ReceiveTime,
|
|
|
|
1 => TimestampMode::Timecode,
|
|
|
|
2 => TimestampMode::Timestamp,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl StaticType for TimestampMode {
|
|
|
|
fn static_type() -> glib::Type {
|
|
|
|
timestamp_mode_get_type()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> glib::value::FromValueOptional<'a> for TimestampMode {
|
|
|
|
unsafe fn from_value_optional(value: &glib::Value) -> Option<Self> {
|
|
|
|
Some(glib::value::FromValue::from_value(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> glib::value::FromValue<'a> for TimestampMode {
|
|
|
|
unsafe fn from_value(value: &glib::Value) -> Self {
|
|
|
|
use glib::translate::ToGlibPtr;
|
|
|
|
|
|
|
|
glib::translate::from_glib(gobject_sys::g_value_get_enum(value.to_glib_none().0))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl glib::value::SetValue for TimestampMode {
|
|
|
|
unsafe fn set_value(value: &mut glib::Value, this: &Self) {
|
|
|
|
use glib::translate::{ToGlib, ToGlibPtrMut};
|
|
|
|
|
|
|
|
gobject_sys::g_value_set_enum(value.to_glib_none_mut().0, this.to_glib())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn timestamp_mode_get_type() -> glib::Type {
|
|
|
|
use std::sync::Once;
|
|
|
|
static ONCE: Once = Once::new();
|
|
|
|
static mut TYPE: glib::Type = glib::Type::Invalid;
|
|
|
|
|
|
|
|
ONCE.call_once(|| {
|
|
|
|
use std::ffi;
|
|
|
|
use std::ptr;
|
|
|
|
|
|
|
|
static mut VALUES: [gobject_sys::GEnumValue; 4] = [
|
|
|
|
gobject_sys::GEnumValue {
|
|
|
|
value: TimestampMode::ReceiveTime as i32,
|
|
|
|
value_name: b"Receive Time\0" as *const _ as *const _,
|
|
|
|
value_nick: b"receive-time\0" as *const _ as *const _,
|
|
|
|
},
|
|
|
|
gobject_sys::GEnumValue {
|
|
|
|
value: TimestampMode::Timecode as i32,
|
|
|
|
value_name: b"NDI Timecode\0" as *const _ as *const _,
|
|
|
|
value_nick: b"timecode\0" as *const _ as *const _,
|
|
|
|
},
|
|
|
|
gobject_sys::GEnumValue {
|
|
|
|
value: TimestampMode::Timestamp as i32,
|
|
|
|
value_name: b"NDI Timestamp\0" as *const _ as *const _,
|
|
|
|
value_nick: b"timestamp\0" as *const _ as *const _,
|
|
|
|
},
|
|
|
|
gobject_sys::GEnumValue {
|
|
|
|
value: 0,
|
|
|
|
value_name: ptr::null(),
|
|
|
|
value_nick: ptr::null(),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
let name = ffi::CString::new("GstNdiTimestampMode").unwrap();
|
|
|
|
unsafe {
|
|
|
|
let type_ = gobject_sys::g_enum_register_static(name.as_ptr(), VALUES.as_ptr());
|
|
|
|
TYPE = glib::translate::from_glib(type_);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
assert_ne!(TYPE, glib::Type::Invalid);
|
|
|
|
TYPE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-11 16:47:03 +00:00
|
|
|
gst_plugin_define!(
|
2019-06-25 16:20:50 +00:00
|
|
|
ndi,
|
2019-07-09 14:51:57 +00:00
|
|
|
env!("CARGO_PKG_DESCRIPTION"),
|
2018-06-27 09:56:11 +00:00
|
|
|
plugin_init,
|
2019-07-09 14:51:57 +00:00
|
|
|
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
2018-12-11 16:47:03 +00:00
|
|
|
"LGPL",
|
2019-07-09 14:51:57 +00:00
|
|
|
env!("CARGO_PKG_NAME"),
|
|
|
|
env!("CARGO_PKG_NAME"),
|
|
|
|
env!("CARGO_PKG_REPOSITORY"),
|
|
|
|
env!("BUILD_REL_DATE")
|
2018-06-27 09:56:11 +00:00
|
|
|
);
|